Compare commits

..

85 Commits

Author SHA1 Message Date
ado
53c5b779d4 [skip ci] Update version 2024-03-14 19:15:32 +01:00
ado
107a122718 [skip ci] Update README 2024-03-14 19:13:03 +01:00
red0124
55d0a4e598
Updated and added new functions related to headers, resolved ODR issues, resolved clang-tidy warnings (#50)
* Bugfix/odr violations (#47)

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

* [skip ci] Fix namespace comments

* Resolve clang-tidy warnings (#48)

* Resolve clang-tidy warnings, update single_header_generator.py

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

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

* Update parser error messages, fix parser tests

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

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

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

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

* Fix test CMakeLists.txt typo
2024-03-14 17:22:57 +01:00
red0124
1b9a01f787
Feature/fuzz (#44)
* Add fuzzing ci, add bedge to README
2024-03-03 20:46:12 +01:00
red0124
f5b750dd93
Merge pull request #43 from red0124/bugfix/ftell_slowdown
Remove usage of ftell when updating cursor position value
2024-03-02 02:20:05 +01:00
ado
7f53b585f9 Remove usage of ftell when updating cursor position value 2024-03-02 00:34:19 +01:00
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
ado
3ea8adedfd Add extended tests with no new line at eof 2024-02-22 00:30:05 +01:00
ado
57ba23c574 [skip ci] Restore extended tests 2024-02-21 21:53:01 +01:00
ado
6516c6cc94 [skip ci] Restore CMAKE_GITHUB_CI, disable some extended tests 2024-02-21 21:38:27 +01:00
ado
cbb0a1ad8e [skip ci] Fix coverage.yml typo, disable extended tests 2024-02-21 21:23:38 +01:00
ado
1798b4c6f3 [skip ci] Update coverage.yml, fix lcov usage error 2024-02-21 20:52:34 +01:00
ado
90a116ac7b [skip ci] Update coverage.yml, add lcov filters 2024-02-21 20:19:42 +01:00
ado
cbbe0acb25 [skip ci] Fix ci_install_lcov.sh 2024-02-21 20:06:32 +01:00
ado
b993eb8852 [skip ci] Update coverage.yml to work with lcov2.0 2024-02-21 19:41:06 +01:00
ado
5e32d722e8 [skip ci] Update coverage.yml, add unit test for files and buffers without new line at the end of the file 2024-02-21 02:19:14 +01:00
ado
59f6591da3 [skip ci] Update coverage.yml 2024-02-21 01:25:53 +01:00
ado
9d96a7d47f [skip ci] Update coverage.yml 2024-02-19 01:59:10 +01:00
ado
d4fc2ee561 [skip ci] Update .gitignore 2024-02-19 01:23:09 +01:00
ado
d422667477 [skip ci] Remove some magic numbers 2024-02-19 01:16:19 +01:00
ado
aaa22046a5 Add null data buffer error handler and unit test, resolve TODOs 2024-02-19 01:00:42 +01:00
ado
775b8c93e2 Fix parser tests part 2 segment issues 2024-02-19 00:16:22 +01:00
ado
e4fba8a918 Split parser tests part 2 into additional segments 2024-02-19 00:04:09 +01:00
ado
11d57bd073 [skip ci] Try fix MINGW ci 2024-02-18 23:40:53 +01:00
ado
45b840a30a [skip ci] Try fix MINGW ci 2024-02-18 23:22:37 +01:00
ado
8bb773625b [skip ci] Try fix MINGW ci 2024-02-18 23:16:10 +01:00
ado
0466c7234c [skip ci] Try fix MINGW ci 2024-02-18 23:13:16 +01:00
ado
d21c387a33 [skip ci] Try fix MINGW ci 2024-02-18 23:07:26 +01:00
ado
d019edb2bf [skip ci] Update unit test CMakeList for MINGW 2024-02-18 21:58:06 +01:00
ado
a2666816de Add maybe_unused attribute to test helper functions 2024-02-18 19:58:25 +01:00
ado
417a03a8a4 Fix unit tests build 2024-02-18 19:36:41 +01:00
ado
baf4317ffa Add more unit tests for buffer mode 2024-02-18 18:58:51 +01:00
ado
63a618957b Add more unit tests for buffer mode 2024-02-18 18:46:26 +01:00
ado
dbaa8131e7 Update ssp.hpp 2024-02-18 15:29:03 +01:00
ado
ef8cf30919 Merge remote-tracking branch 'origin/master' into feature/csv_buffer 2024-02-18 15:27:46 +01:00
ado
42d618ed64 Make CI unit tests run sequentially 2024-02-18 14:12:10 +01:00
ado
8b07f7d6cb Remove tmpnam usage for random file generation 2024-02-18 13:55:14 +01:00
ado
82f8ed12b4 Fix out of bounds reading for get_line_buffer, write more buffer mode unit tests 2024-02-18 11:20:36 +01:00
ado
e89e268280 Update test helpers random file name generator 2024-02-17 22:21:37 +01:00
ado
aacf690640 Fix unit test CMakeLists typo 2024-02-17 21:10:25 +01:00
ado
f8c5757d99 Fix unit test CMakeLists 2024-02-17 21:08:24 +01:00
ado
ce03c371ae Split parser tests into multiple files, add more tests for buffer mode 2024-02-17 21:05:30 +01:00
ado
6c859959d6 Add more unit tests for buffer mode 2024-02-17 17:43:33 +01:00
ado
4434db29f6 Merge remote-tracking branch 'origin/master' into feature/csv_buffer 2024-02-17 03:39:11 +01:00
ado
7062888d72 Fix msvc build 2024-02-17 01:07:29 +01:00
ado
f04ede3a49 Add option to read csv data from a buffer, add some unit tests for the new feature 2024-02-17 00:55:36 +01:00
51 changed files with 4088 additions and 2624 deletions

7
.github/fuzz/makefile vendored Normal file
View File

@ -0,0 +1,7 @@
EXE=ssp_fuzz
all:
clang++ ${CXXFLAGS} ssp_fuzz.cpp -fsanitize=fuzzer -std=c++17 -o ${EXE}
run:
./${EXE} -max_total_time=900

81
.github/fuzz/ssp_fuzz.cpp vendored Normal file
View File

@ -0,0 +1,81 @@
#include "../../ssp.hpp"
#include <filesystem>
#include <iostream>
#include <unistd.h>
template <typename... Ts>
void test_ssp_file_mode(const uint8_t* data, size_t size,
std::string delim = ss::default_delimiter) {
std::string file_name = std::filesystem::temp_directory_path().append(
"ss_fuzzer" + std::to_string(getpid()) + ".csv");
FILE* file = std::fopen(file_name.c_str(), "wb");
if (!file) {
std::exit(1);
}
std::fwrite(data, size, 1, file);
std::fclose(file);
ss::parser<Ts...> p{file_name.c_str(), delim};
while (!p.eof()) {
try {
const auto& [s0, s1] =
p.template get_next<std::string, std::string>();
if (s0.size() == 10000) {
std::cout << s0.size() << std::endl;
}
} catch (ss::exception& e) {
continue;
}
}
std::remove(file_name.c_str());
}
template <typename... Ts>
void test_ssp_buffer_mode(const uint8_t* data, size_t size,
std::string delim = ss::default_delimiter) {
ss::parser<Ts...> p{(const char*)data, size, delim};
while (!p.eof()) {
try {
const auto& [s0, s1] =
p.template get_next<std::string, std::string>();
if (s0.size() == 10000) {
std::cout << s0.size() << std::endl;
}
} catch (ss::exception& e) {
continue;
}
}
}
template <typename... Ts>
void test_ssp(const uint8_t* data, size_t size) {
test_ssp_file_mode<Ts...>(data, size);
test_ssp_file_mode<Ts..., ss::throw_on_error>(data, size);
test_ssp_file_mode<Ts...>(data, size, ":::");
test_ssp_file_mode<Ts..., ss::throw_on_error>(data, size, ":::");
test_ssp_buffer_mode<Ts...>(data, size);
test_ssp_buffer_mode<Ts..., ss::throw_on_error>(data, size);
test_ssp_buffer_mode<Ts...>(data, size, ":::");
test_ssp_buffer_mode<Ts..., ss::throw_on_error>(data, size, ":::");
}
extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) {
using escape = ss::escape<'\\'>;
using quote = ss::quote<'"'>;
using trim = ss::trim<' ', '\t'>;
using multiline_r = ss::multiline_restricted<5>;
test_ssp<>(data, size);
test_ssp<escape>(data, size);
test_ssp<quote>(data, size);
test_ssp<trim>(data, size);
test_ssp<quote, escape>(data, size);
test_ssp<escape, quote, multiline_r, trim>(data, size);
test_ssp<escape, quote, multiline_r, trim, ss::ignore_empty>(data, size);
return 0;
}

View File

@ -46,21 +46,24 @@ jobs:
- name: Install test coverage tools
run: |
apt update
apt install -y gcovr lcov
apt install -y gcovr
- name: Install lcov2.0
run: script/ci_install_lcov.sh
- name: Configure
run: cmake -S test -B build -D CMAKE_BUILD_TYPE=Debug -D CMAKE_CXX_FLAGS="-Wall -fprofile-arcs -ftest-coverage --coverage"
run: cmake -S test -B build -D CMAKE_BUILD_TYPE=Debug -D CMAKE_CXX_FLAGS="-Wall -fprofile-arcs -ftest-coverage --coverage -fno-elide-constructors -fno-default-inline"
- name: Build
run: cmake --build build -j ${{steps.cores.outputs.count}}
- name: Run
working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}}
run: ctest --output-on-failure
- name: Generate coverage report
run: |
lcov -d . -c -o out.info --rc lcov_branch_coverage=1 --no-external
lcov -d . -c -o out.info --rc branch_coverage=1 --no-external --filter branch --filter line --ignore-errors mismatch
lcov -e out.info '*include/ss*hpp' -o filtered.info
- name: Invoke coveralls

43
.github/workflows/fuzz.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: fuzz-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]')
runs-on: ubuntu-latest
name: "Fuzzing"
container:
image: silkeh/clang:15
options: -v /usr/local:/host_usr_local
steps:
- uses: actions/checkout@v1
- name: Build
working-directory: .github/fuzz
run: make
- name: Run
working-directory: .github/fuzz
run: make run

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

@ -66,4 +66,4 @@ jobs:
- name: Run
working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}}
run: ctest --output-on-failure

View File

@ -56,4 +56,4 @@ jobs:
- name: Run
working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}}
run: ctest --output-on-failure

View File

@ -74,4 +74,4 @@ jobs:
- name: Run
working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}}
run: ctest --output-on-failure

View File

@ -62,4 +62,4 @@ jobs:
- name: Run
working-directory: build
run: >-
ctest -C Debug --output-on-failure -j ${{steps.cores.outputs.count}}
ctest -C Debug --output-on-failure

View File

@ -73,4 +73,4 @@ jobs:
- name: Run
working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}}
run: ctest --output-on-failure

View File

@ -71,4 +71,4 @@ jobs:
- name: Run
working-directory: build
run: ctest --output-on-failure -j ${{steps.cores.outputs.count}}
run: ctest --output-on-failure

6
.gitignore vendored
View File

@ -1,10 +1,10 @@
compile_commands.json
.clang-format
.ccls-cache/*
.clang-tidy
.ccls-cache/
.cache/
experiment/
build/
hbuild/
subprojects/*
!subprojects/*.wrap
ssp.cpp
ssp.bin

View File

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

View File

@ -9,6 +9,7 @@
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![coverage](https://coveralls.io/repos/github/red0124/ssp/badge.svg?branch=master)](https://coveralls.io/github/red0124/ssp?branch=master)
[![fuzz](https://github.com/red0124/ssp/workflows/fuzz-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/fuzz.yml)
[![single-header](https://github.com/red0124/ssp/workflows/single-header-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/single-header.yml)
[![ubuntu-latest-gcc](https://github.com/red0124/ssp/workflows/ubuntu-latest-gcc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-gcc.yml)
[![ubuntu-latest-clang](https://github.com/red0124/ssp/workflows/ubuntu-latest-clang-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-clang.yml)
@ -16,14 +17,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 +60,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 +74,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 significant performance loss when converting floating point values since the **`fast_float`** library is not present within the file.
# Installation
@ -89,7 +92,7 @@ The library supports [CMake](#Cmake) and [meson](#Meson) build systems
## Headers
The parser can be told to use only certain columns by parsing the header. This can be done by using the **`use_fields`** method. It accepts any number of string-like arguments or even an **`std::vector<std::string>`** with the field names. If any of the fields are not found within the header or if any fields are defined multiple times it will result in an error.
The parser can be told to use only certain columns by parsing the header. This can be done with the **`use_fields`** method. It accepts any number of string-like arguments or even an **`std::vector<std::string>`** with the field names. If any of the fields are not found within the header or if any fields are defined multiple times it will result in an error.
```shell
$ cat students_with_header.csv
Id,Age,Grade
@ -113,18 +116,18 @@ 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. If the header has been ignored calling any method related to header usage will result in a compilation error.
```cpp
ss::parser<ss::ignore_header> p{file_name};
```
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"};
p.use_fields("Id", "Grade");
p.use_fields("Grade");
const auto& [id, grade] = p.get_next<std::string, float>();
std::cout << id << ' ' << grade << std::endl;
const auto& grade = p.get_next<std::string>();
std::cout << grade << std::endl;
if (p.field_exists("Id")) {
p.use_fields("Grade", "Id");
@ -136,10 +139,32 @@ The fields with which the parser works with can be modified at any given time. T
```
```shell
$ ./a.out
James Bailey 2.5
40 Brian S. Wolfe
65 Bill (Heath) Gates
2.5
1.9 Brian S. Wolfe
3.3 Bill (Heath) Gates
```
The header is parsed with the same rules as other rows, the only difference is that **`multiline`** will be disabled when parsing the header. To get the data that is
present in the header as a **`std::vector<std::string>`**, the **`header`** method can be used, and to get the header line before it has been parsed, the **`raw_header`** method can be used:
```cpp
// ...
ss::parser<ss::throw_on_error> p{"students_with_header.csv"};
std::cout << p.raw_header() << std::endl;
for (const auto& field: p.header()) {
std::cout << "> " << field << std::endl;
}
// ...
```
```shell
$ ./a.out
Id,Age,Grade
> Id
> Age
> Grade
```
Methods related to headers can also fail, the error handling of these is done in the same way as for other methods.
## Conversions
An alternate loop to the example above would look like:
```cpp
@ -158,7 +183,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 +227,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 +272,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 +328,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 +379,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 +421,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 +498,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 +519,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,9 +1,15 @@
#pragma once
#include <cstdint>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <vector>
#if !__unix__
#include <array>
#include <cstdint>
#endif
namespace ss {
struct none {};
@ -12,73 +18,134 @@ using string_range = std::pair<const char*, const char*>;
using split_data = std::vector<string_range>;
constexpr inline auto default_delimiter = ",";
constexpr inline auto get_line_initial_buffer_size = 128;
template <bool StringError>
inline void assert_string_error_defined() {
void assert_string_error_defined() {
static_assert(StringError,
"'string_error' needs to be enabled to use 'error_msg'");
}
template <bool ThrowOnError>
inline void assert_throw_on_error_not_defined() {
void assert_throw_on_error_not_defined() {
static_assert(!ThrowOnError, "cannot handle errors manually if "
"'throw_on_error' is enabled");
}
[[nodiscard]] 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(char** lineptr, size_t* n, FILE* stream) {
return getline(lineptr, n, stream);
[[nodiscard]] 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(char** lineptr, size_t* n, FILE* stream) {
size_t pos;
int c;
if (lineptr == nullptr || stream == nullptr || n == nullptr) {
errno = EINVAL;
return -1;
using ssize_t = intptr_t;
[[nodiscard]] inline ssize_t get_line_file(char*& lineptr, size_t& n,
FILE* file) {
std::array<char, get_line_initial_buffer_size> buff;
if (lineptr == nullptr || n < sizeof(buff)) {
const 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';
size_t line_used = 0;
while (std::fgets(buff.data(), sizeof(buff), file) != nullptr) {
line_used = std::strlen(lineptr);
size_t buff_used = std::strlen(buff.data());
if (n <= buff_used + line_used) {
const size_t new_n = n * 2;
lineptr = static_cast<char*>(strict_realloc(lineptr, new_n));
n = new_n;
}
if (*lineptr == nullptr) {
*lineptr = static_cast<char*>(malloc(128));
if (*lineptr == nullptr) {
return -1;
std::memcpy(lineptr + line_used, buff.data(), buff_used);
line_used += buff_used;
lineptr[line_used] = '\0';
if (lineptr[line_used - 1] == '\n') {
return line_used;
}
*n = 128;
}
pos = 0;
while (c != EOF) {
if (pos + 1 >= *n) {
size_t new_size = *n + (*n >> 2);
if (new_size < 128) {
new_size = 128;
}
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;
return (line_used != 0) ? line_used : -1;
}
(*lineptr)[pos++] = c;
if (c == '\n') {
break;
}
c = getc(stream);
}
(*lineptr)[pos] = '\0';
return pos;
}
#endif
} /* ss */
[[nodiscard]] inline ssize_t get_line_buffer(char*& lineptr, size_t& n,
const char* const csv_data_buffer,
size_t csv_data_size,
size_t& curr_char) {
if (curr_char >= csv_data_size) {
return -1;
}
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) {
const 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;
}
}
lineptr[line_used] = '\0';
return line_used;
}
[[nodiscard]] inline std::tuple<ssize_t, bool> get_line(
char*& buffer, size_t& buffer_size, FILE* file,
const char* const csv_data_buffer, size_t csv_data_size,
size_t& curr_char) {
ssize_t ssize = 0;
if (file) {
ssize = get_line_file(buffer, buffer_size, file);
curr_char += ssize;
} 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};
}
} /* namespace ss */

View File

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

View File

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

View File

@ -2,8 +2,8 @@
#include "type_traits.hpp"
#include <charconv>
#include <cstdint>
#include <cstring>
#include <functional>
#include <optional>
#include <string>
#include <string_view>
@ -13,6 +13,7 @@
#include <fast_float/fast_float.h>
#else
#include <algorithm>
#include <array>
#include <cstdlib>
#endif
@ -25,8 +26,8 @@ namespace ss {
#ifndef SSP_DISABLE_FAST_FLOAT
template <typename T>
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
const char* const begin, const char* const end) {
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>>
to_num(const char* const begin, const char* const end) {
T ret;
auto [ptr, ec] = fast_float::from_chars(begin, end, ret);
@ -39,22 +40,23 @@ std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
#else
template <typename T>
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
const char* const begin, const char* const end) {
[[nodiscard]] std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>>
to_num(const char* const begin, const char* const end) {
static_assert(!std::is_same_v<T, long double>,
"Conversion to long double is disabled");
constexpr static auto buff_max = 64;
char short_buff[buff_max];
size_t string_range = std::distance(begin, end);
std::array<char, buff_max> short_buff;
const size_t string_range = std::distance(begin, end);
std::string long_buff;
char* buff;
char* buff = nullptr;
if (string_range > buff_max) {
long_buff = std::string{begin, end};
buff = long_buff.data();
} else {
buff = short_buff;
buff = short_buff.data();
buff[string_range] = '\0';
std::copy_n(begin, string_range, buff);
}
@ -77,8 +79,42 @@ std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
#endif
////////////////
// numeric_wrapper
////////////////
template <typename T>
std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
struct numeric_wrapper {
using type = T;
numeric_wrapper() = default;
numeric_wrapper(numeric_wrapper&&) noexcept = default;
numeric_wrapper(const numeric_wrapper&) = default;
numeric_wrapper& operator=(numeric_wrapper&&) noexcept = default;
numeric_wrapper& operator=(const numeric_wrapper&) = default;
~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>
[[nodiscard]] std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
const char* const begin, const char* const end) {
T ret;
auto [ptr, ec] = std::from_chars(begin, end, ret);
@ -89,6 +125,19 @@ std::enable_if_t<std::is_integral_v<T>, std::optional<T>> to_num(
return ret;
}
template <typename T>
[[nodiscard]] std::enable_if_t<is_instance_of_v<numeric_wrapper, T>,
std::optional<T>>
to_num(const char* const begin, const char* const end) {
T ret;
auto [ptr, ec] = std::from_chars(begin, end, ret.value);
if (ec != std::errc() || ptr != end) {
return std::nullopt;
}
return ret;
}
////////////////
// extract
////////////////
@ -98,12 +147,14 @@ template <typename T>
struct unsupported_type {
constexpr static bool value = false;
};
} /* namespace */
} /* namespace errors */
template <typename T>
std::enable_if_t<!std::is_integral_v<T> && !std::is_floating_point_v<T> &&
[[nodiscard]] std::enable_if_t<!std::is_integral_v<T> &&
!std::is_floating_point_v<T> &&
!is_instance_of_v<std::optional, T> &&
!is_instance_of_v<std::variant, T>,
!is_instance_of_v<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 +163,10 @@ 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>
[[nodiscard]] std::enable_if_t<std::is_integral_v<T> ||
std::is_floating_point_v<T> ||
is_instance_of_v<numeric_wrapper, T>,
bool>
extract(const char* begin, const char* end, T& value) {
auto optional_value = to_num<T>(begin, end);
if (!optional_value) {
@ -123,8 +177,8 @@ extract(const char* begin, const char* end, T& value) {
}
template <typename T>
std::enable_if_t<is_instance_of_v<std::optional, T>, bool> extract(
const char* begin, const char* end, T& value) {
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::optional, T>, bool>
extract(const char* begin, const char* end, T& value) {
typename T::value_type raw_value;
if (extract(begin, end, raw_value)) {
value = raw_value;
@ -135,7 +189,8 @@ std::enable_if_t<is_instance_of_v<std::optional, T>, bool> extract(
}
template <typename T, size_t I>
bool extract_variant(const char* begin, const char* end, T& value) {
[[nodiscard]] bool extract_variant(const char* begin, const char* end,
T& value) {
using IthType = std::variant_alternative_t<I, std::decay_t<T>>;
IthType ithValue;
if (extract<IthType>(begin, end, ithValue)) {
@ -148,7 +203,7 @@ bool extract_variant(const char* begin, const char* end, T& value) {
}
template <typename T>
std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
[[nodiscard]] std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
const char* begin, const char* end, T& value) {
return extract_variant<T, 0>(begin, end, value);
}
@ -158,7 +213,8 @@ std::enable_if_t<is_instance_of_v<std::variant, T>, bool> extract(
////////////////
template <>
inline bool extract(const char* begin, const char* end, bool& value) {
[[nodiscard]] inline bool extract(const char* begin, const char* end,
bool& value) {
if (end == begin + 1) {
if (*begin == '1') {
value = true;
@ -168,10 +224,13 @@ inline bool extract(const char* begin, const char* end, bool& value) {
return false;
}
} else {
size_t size = end - begin;
if (size == 4 && strncmp(begin, "true", size) == 0) {
constexpr static auto true_size = 4;
constexpr static auto false_size = 5;
const size_t size = end - begin;
if (size == true_size && std::strncmp(begin, "true", size) == 0) {
value = true;
} else if (size == 5 && strncmp(begin, "false", size) == 0) {
} else if (size == false_size &&
std::strncmp(begin, "false", size) == 0) {
value = false;
} else {
return false;
@ -182,22 +241,24 @@ inline bool extract(const char* begin, const char* end, bool& value) {
}
template <>
inline bool extract(const char* begin, const char* end, char& value) {
[[nodiscard]] inline bool extract(const char* begin, const char* end,
char& value) {
value = *begin;
return (end == begin + 1);
}
template <>
inline bool extract(const char* begin, const char* end, std::string& value) {
[[nodiscard]] inline bool extract(const char* begin, const char* end,
std::string& value) {
value = std::string{begin, end};
return true;
}
template <>
inline bool extract(const char* begin, const char* end,
[[nodiscard]] inline bool extract(const char* begin, const char* end,
std::string_view& value) {
value = std::string_view{begin, static_cast<size_t>(end - begin)};
return true;
}
} /* ss */
} /* namespace ss */

View File

@ -2,7 +2,6 @@
#include <cstdlib>
#include <functional>
#include <tuple>
namespace ss {
@ -77,4 +76,4 @@ struct member_wrapper<R T::*> {
template <typename T> \
constexpr bool has_m_##method##_t = has_m_##method<T>::value;
} /* trait */
} /* namespace ss */

View File

@ -31,10 +31,12 @@ class parser {
constexpr static bool ignore_empty = setup<Options...>::ignore_empty;
using header_splitter = ss::splitter<
ss::filter_not_t<ss::is_instance_of_multiline, Options...>>;
public:
parser(const std::string& file_name,
const std::string& delim = ss::default_delimiter)
: file_name_{file_name}, reader_{file_name_, delim} {
parser(std::string file_name, std::string delim = ss::default_delimiter)
: file_name_{std::move(file_name)}, reader_{file_name_, delim} {
if (reader_.file_) {
read_line();
if constexpr (ignore_header) {
@ -48,14 +50,32 @@ public:
}
}
parser(parser&& other) = default;
parser& operator=(parser&& other) = default;
parser(const char* const csv_data_buffer, size_t csv_data_size,
const std::string& delim = ss::default_delimiter)
: file_name_{"CSV data buffer"},
reader_{csv_data_buffer, csv_data_size, delim} {
if (csv_data_buffer) {
read_line();
if constexpr (ignore_header) {
ignore_next();
} else {
raw_header_ = reader_.get_buffer();
}
} else {
handle_error_null_buffer();
eof_ = true;
}
}
parser(parser&& other) noexcept = default;
parser& operator=(parser&& other) noexcept = default;
~parser() = default;
parser() = delete;
parser(const parser& other) = delete;
parser& operator=(const parser& other) = delete;
bool valid() const {
[[nodiscard]] bool valid() const {
if constexpr (string_error) {
return error_.empty();
} else if constexpr (throw_on_error) {
@ -65,12 +85,12 @@ public:
}
}
const std::string& error_msg() const {
[[nodiscard]] const std::string& error_msg() const {
assert_string_error_defined<string_error>();
return error_;
}
bool eof() const {
[[nodiscard]] bool eof() const {
return eof_;
}
@ -79,19 +99,21 @@ public:
}
template <typename T, typename... Ts>
T get_object() {
[[nodiscard]] T get_object() {
return to_object<T>(get_next<Ts...>());
}
size_t line() const {
return reader_.line_number_ > 1 ? reader_.line_number_ - 1
[[nodiscard]] size_t line() const {
return reader_.line_number_ > 0 ? reader_.line_number_ - 1
: reader_.line_number_;
}
template <typename T, typename... Ts>
no_void_validator_tup_t<T, Ts...> get_next() {
std::optional<std::string> error;
[[nodiscard]] size_t position() const {
return reader_.chars_read_;
}
template <typename T, typename... Ts>
[[nodiscard]] no_void_validator_tup_t<T, Ts...> get_next() {
if (!eof_) {
if constexpr (throw_on_error) {
try {
@ -140,20 +162,49 @@ public:
return value;
}
bool field_exists(const std::string& field) {
[[nodiscard]] std::string raw_header() const {
assert_ignore_header_not_defined();
return raw_header_;
}
[[nodiscard]] std::vector<std::string> header() {
assert_ignore_header_not_defined();
clear_error();
header_splitter splitter;
std::string raw_header_copy = raw_header_;
if (!strict_split(splitter, raw_header_copy)) {
return {};
}
std::vector<std::string> split_header;
for (const auto& [begin, end] : splitter.get_split_data()) {
split_header.emplace_back(begin, end);
}
return split_header;
}
[[nodiscard]] bool field_exists(const std::string& field) {
assert_ignore_header_not_defined();
clear_error();
if (header_.empty()) {
split_header_data();
}
if (!valid()) {
return false;
}
return header_index(field).has_value();
}
template <typename... Ts>
void use_fields(const Ts&... fields_args) {
if constexpr (ignore_header) {
handle_error_header_ignored();
return;
}
assert_ignore_header_not_defined();
clear_error();
if (header_.empty() && !eof()) {
split_header_data();
@ -166,7 +217,7 @@ public:
auto fields = std::vector<std::string>{fields_args...};
if (fields.empty()) {
handle_error_empty_mapping();
handle_error_invalid_use_fields_argument();
return;
}
@ -192,7 +243,7 @@ public:
reader_.next_line_converter_.set_column_mapping(column_mappings,
header_.size());
if (line() == 1) {
if (line() == 0) {
ignore_next();
}
}
@ -214,13 +265,17 @@ public:
}
iterator(const iterator& other) = default;
iterator(iterator&& other) = default;
iterator(iterator&& other) noexcept = default;
~iterator() = default;
value& operator*() {
iterator& operator=(const iterator& other) = delete;
iterator& operator=(iterator&& other) noexcept = delete;
[[nodiscard]] value& operator*() {
return value_;
}
value* operator->() {
[[nodiscard]] value* operator->() {
return &value_;
}
@ -239,17 +294,21 @@ public:
return *this;
}
iterator& operator++(int) {
return ++*this;
iterator operator++(int) {
auto result = *this;
++*this;
return result;
}
friend bool operator==(const iterator& lhs, const iterator& rhs) {
[[nodiscard]] friend bool operator==(const iterator& lhs,
const iterator& rhs) {
return (lhs.parser_ == nullptr && rhs.parser_ == nullptr) ||
(lhs.parser_ == rhs.parser_ &&
&lhs.value_ == &rhs.value_);
}
friend bool operator!=(const iterator& lhs, const iterator& rhs) {
[[nodiscard]] friend bool operator!=(const iterator& lhs,
const iterator& rhs) {
return !(lhs == rhs);
}
@ -261,11 +320,11 @@ public:
iterable(parser<Options...>* parser) : parser_{parser} {
}
iterator begin() {
[[nodiscard]] iterator begin() {
return ++iterator{parser_};
}
iterator end() {
[[nodiscard]] iterator end() {
return iterator{};
}
@ -274,12 +333,12 @@ public:
};
template <typename... Ts>
auto iterate() {
[[nodiscard]] auto iterate() {
return iterable<false, Ts...>{this};
}
template <typename... Ts>
auto iterate_object() {
[[nodiscard]] auto iterate_object() {
return iterable<true, Ts...>{this};
}
@ -304,7 +363,7 @@ public:
Fun&& fun = none{}) {
using Value = no_void_validator_tup_t<Us...>;
std::optional<Value> value;
try_convert_and_invoke<Value, Us...>(value, fun);
try_convert_and_invoke<Value, Us...>(value, std::forward<Fun>(fun));
return composite_with(std::move(value));
}
@ -313,11 +372,11 @@ public:
template <typename U, typename... Us, typename Fun = none>
composite<Ts..., std::optional<U>> or_object(Fun&& fun = none{}) {
std::optional<U> value;
try_convert_and_invoke<U, Us...>(value, fun);
try_convert_and_invoke<U, Us...>(value, std::forward<Fun>(fun));
return composite_with(std::move(value));
}
std::tuple<Ts...> values() {
[[nodiscard]] std::tuple<Ts...> values() {
return values_;
}
@ -340,7 +399,7 @@ public:
private:
template <typename T>
composite<Ts..., T> composite_with(T&& new_value) {
[[nodiscard]] composite<Ts..., T> composite_with(T&& new_value) {
auto merged_values =
std::tuple_cat(std::move(values_),
std::tuple<T>{parser_.valid()
@ -351,7 +410,10 @@ public:
template <typename U, typename... Us, typename Fun = none>
void try_convert_and_invoke(std::optional<U>& value, Fun&& fun) {
if (!parser_.valid()) {
if (parser_.valid()) {
return;
}
auto tuple_output = try_same<Us...>();
if (!parser_.valid()) {
return;
@ -365,10 +427,9 @@ public:
parser_.try_invoke(*value, std::forward<Fun>(fun));
}
}
template <typename U, typename... Us>
no_void_validator_tup_t<U, Us...> try_same() {
[[nodiscard]] no_void_validator_tup_t<U, Us...> try_same() {
parser_.clear_error();
auto value =
parser_.reader_.converter_.template convert<U, Us...>();
@ -389,8 +450,8 @@ public:
// tries to convert a line and returns a composite which is
// able to try additional conversions in case of failure
template <typename... Ts, typename Fun = none>
composite<std::optional<no_void_validator_tup_t<Ts...>>> try_next(
Fun&& fun = none{}) {
[[nodiscard]] composite<std::optional<no_void_validator_tup_t<Ts...>>>
try_next(Fun&& fun = none{}) {
assert_throw_on_error_not_defined<throw_on_error>();
using Ret = no_void_validator_tup_t<Ts...>;
return try_invoke_and_make_composite<
@ -400,7 +461,7 @@ public:
// identical to try_next but returns composite with object instead of a
// tuple
template <typename T, typename... Ts, typename Fun = none>
composite<std::optional<T>> try_object(Fun&& fun = none{}) {
[[nodiscard]] composite<std::optional<T>> try_object(Fun&& fun = none{}) {
assert_throw_on_error_not_defined<throw_on_error>();
return try_invoke_and_make_composite<
std::optional<T>>(get_object<T, Ts...>(), std::forward<Fun>(fun));
@ -419,7 +480,8 @@ private:
using Ret = decltype(try_invoke_impl(arg, std::forward<Fun>(fun)));
constexpr bool returns_void = std::is_same_v<Ret, void>;
if constexpr (!returns_void) {
if (!try_invoke_impl(arg, std::forward<Fun>(fun))) {
if (!try_invoke_impl(std::forward<Arg>(arg),
std::forward<Fun>(fun))) {
handle_error_failed_check();
}
} else {
@ -450,26 +512,55 @@ private:
}
template <typename T, typename Fun = none>
composite<T> try_invoke_and_make_composite(T&& value, Fun&& fun) {
[[nodiscard]] composite<T> try_invoke_and_make_composite(T&& value,
Fun&& fun) {
if (valid()) {
try_invoke(*value, std::forward<Fun>(fun));
}
return {valid() ? std::move(value) : std::nullopt, *this};
return {valid() ? std::forward<T>(value) : std::nullopt, *this};
}
////////////////
// header
////////////////
void assert_ignore_header_not_defined() const {
static_assert(!ignore_header,
"cannot use this method when 'ignore_header' is defined");
}
[[nodiscard]] bool strict_split(header_splitter& splitter,
std::string& header) {
if constexpr (throw_on_error) {
try {
splitter.split(header.data(), reader_.delim_);
} catch (const ss::exception& e) {
decorate_rethrow_invalid_header_split(e);
}
} else {
splitter.split(header.data(), reader_.delim_);
if (!splitter.valid()) {
handle_error_invalid_header_split(splitter);
return false;
}
}
return true;
}
void split_header_data() {
ss::splitter<Options...> splitter;
header_splitter splitter;
std::string raw_header_copy = raw_header_;
splitter.split(raw_header_copy.data(), reader_.delim_);
for (const auto& [begin, end] : splitter.split_data_) {
if (!strict_split(splitter, raw_header_copy)) {
return;
}
for (const auto& [begin, end] : splitter.get_split_data()) {
std::string field{begin, end};
if (std::find(header_.begin(), header_.end(), field) !=
header_.end()) {
handle_error_invalid_header(field);
handle_error_duplicate_header_field(field);
header_.clear();
return;
}
@ -477,7 +568,7 @@ private:
}
}
std::optional<size_t> header_index(const std::string& field) {
[[nodiscard]] std::optional<size_t> header_index(const std::string& field) {
auto it = std::find(header_.begin(), header_.end(), field);
if (it == header_.end()) {
@ -500,7 +591,20 @@ private:
}
void handle_error_failed_check() {
constexpr static auto error_msg = " failed check";
constexpr static auto error_msg = ": failed check";
if constexpr (string_error) {
error_.clear();
error_.append(file_name_).append(error_msg);
} else if constexpr (throw_on_error) {
throw ss::exception{file_name_ + error_msg};
} else {
error_ = true;
}
}
void handle_error_null_buffer() {
constexpr static auto error_msg = ": received null data buffer";
if constexpr (string_error) {
error_.clear();
@ -513,7 +617,7 @@ private:
}
void handle_error_file_not_open() {
constexpr static auto error_msg = " could not be opened";
constexpr static auto error_msg = ": could not be opened";
if constexpr (string_error) {
error_.clear();
@ -526,7 +630,7 @@ private:
}
void handle_error_eof_reached() {
constexpr static auto error_msg = " read on end of file";
constexpr static auto error_msg = ": read on end of file";
if constexpr (string_error) {
error_.clear();
@ -551,20 +655,6 @@ private:
}
}
void handle_error_header_ignored() {
constexpr static auto error_msg =
": the header row is ignored within the setup it cannot be used";
if constexpr (string_error) {
error_.clear();
error_.append(file_name_).append(error_msg);
} else if constexpr (throw_on_error) {
throw ss::exception{file_name_ + error_msg};
} else {
error_ = true;
}
}
void handle_error_invalid_field(const std::string& field) {
constexpr static auto error_msg =
": header does not contain given field: ";
@ -592,8 +682,9 @@ private:
}
}
void handle_error_empty_mapping() {
constexpr static auto error_msg = "received empty mapping";
void handle_error_invalid_use_fields_argument() {
constexpr static auto error_msg =
"received invalid argument for 'use_fields'";
if constexpr (string_error) {
error_.clear();
@ -605,19 +696,53 @@ private:
}
}
void handle_error_invalid_header(const std::string& field) {
constexpr static auto error_msg = "header contains duplicates: ";
void handle_error_invalid_header_field() {
constexpr static auto error_msg = ": header contains empty field";
if constexpr (string_error) {
error_.clear();
error_.append(error_msg).append(error_msg);
error_.append(file_name_).append(error_msg);
} else if constexpr (throw_on_error) {
throw ss::exception{error_msg + field};
throw ss::exception{file_name_ + error_msg};
} else {
error_ = true;
}
}
void handle_error_duplicate_header_field(const std::string& field) {
constexpr static auto error_msg = ": header contains duplicate: ";
if constexpr (string_error) {
error_.clear();
error_.append(file_name_).append(error_msg).append(field);
} else if constexpr (throw_on_error) {
throw ss::exception{file_name_ + error_msg + field};
} else {
error_ = true;
}
}
void handle_error_invalid_header_split(const header_splitter& splitter) {
constexpr static auto error_msg = ": failed header parsing: ";
if constexpr (string_error) {
error_.clear();
error_.append(file_name_)
.append(error_msg)
.append(splitter.error_msg());
} else {
error_ = true;
}
}
void decorate_rethrow_invalid_header_split(const ss::exception& e) const {
static_assert(throw_on_error,
"throw_on_error needs to be enabled to use this method");
throw ss::exception{std::string{file_name_}
.append(": failed header parsing: ")
.append(e.what())};
}
void decorate_rethrow(const ss::exception& e) const {
static_assert(throw_on_error,
"throw_on_error needs to be enabled to use this method");
@ -637,29 +762,39 @@ private:
}
struct reader {
reader(const std::string& file_name_, const std::string& delim)
: delim_{delim}, file_{fopen(file_name_.c_str(), "rb")} {
reader(const std::string& file_name_, std::string delim)
: delim_{std::move(delim)},
file_{std::fopen(file_name_.c_str(), "rb")} {
}
reader(reader&& other)
reader(const char* const buffer, size_t csv_data_size,
std::string delim)
: delim_{std::move(delim)}, csv_data_buffer_{buffer},
csv_data_size_{csv_data_size} {
}
reader(reader&& other) noexcept
: buffer_{other.buffer_},
next_line_buffer_{other.next_line_buffer_},
helper_buffer_{other.helper_buffer_}, converter_{std::move(
other.converter_)},
helper_buffer_{other.helper_buffer_},
converter_{std::move(other.converter_)},
next_line_converter_{std::move(other.next_line_converter_)},
buffer_size_{other.buffer_size_},
next_line_buffer_size_{other.next_line_buffer_size_},
helper_size_{other.helper_size_}, delim_{std::move(other.delim_)},
file_{other.file_}, crlf_{other.crlf_},
line_number_{other.line_number_}, next_line_size_{
other.next_line_size_} {
helper_buffer_size{other.helper_buffer_size},
delim_{std::move(other.delim_)}, file_{other.file_},
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_}, 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;
}
reader& operator=(reader&& other) {
reader& operator=(reader&& other) noexcept {
if (this != &other) {
buffer_ = other.buffer_;
next_line_buffer_ = other.next_line_buffer_;
@ -668,29 +803,34 @@ private:
next_line_converter_ = std::move(other.next_line_converter_);
buffer_size_ = other.buffer_size_;
next_line_buffer_size_ = other.next_line_buffer_size_;
helper_size_ = other.helper_size_;
helper_buffer_size = other.helper_buffer_size;
delim_ = std::move(other.delim_);
file_ = other.file_;
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_;
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::ignore = std::fclose(file_);
}
}
@ -699,19 +839,21 @@ private:
reader& operator=(const reader& other) = delete;
// read next line each time in order to set eof_
bool read_next() {
[[nodiscard]] bool read_next() {
next_line_converter_.clear_error();
ssize_t ssize = 0;
size_t size = 0;
while (size == 0) {
++line_number_;
if (next_line_buffer_size_ > 0) {
next_line_buffer_[0] = '\0';
}
ssize = get_line(&next_line_buffer_, &next_line_buffer_size_,
file_);
if (ssize == -1) {
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 (eof) {
return false;
}
@ -736,7 +878,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;
}
@ -754,7 +897,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;
}
@ -765,8 +909,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;
@ -786,7 +931,7 @@ private:
std::swap(converter_, next_line_converter_);
}
bool multiline_limit_reached(size_t& limit) {
[[nodiscard]] bool multiline_limit_reached(size_t& limit) {
if constexpr (multiline::size > 0) {
if (limit++ >= multiline::size) {
next_line_converter_.handle_error_multiline_limit_reached();
@ -796,8 +941,8 @@ private:
return false;
}
bool escaped_eol(size_t size) {
const char* curr;
[[nodiscard]] bool escaped_eol(size_t size) {
const char* curr = nullptr;
for (curr = next_line_buffer_ + size - 1;
curr >= next_line_buffer_ &&
setup<Options...>::escape::match(*curr);
@ -806,21 +951,27 @@ private:
return (next_line_buffer_ - curr + size) % 2 == 0;
}
bool unterminated_quote() {
[[nodiscard]] bool unterminated_quote() {
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) {
[[nodiscard]] size_t remove_eol(char*& buffer, size_t ssize) {
if (buffer[ssize - 1] != '\n') {
crlf_ = false;
return ssize;
}
size_t size = ssize - 1;
if (ssize >= 2 && buffer[ssize - 2] == '\r') {
crlf_ = true;
@ -834,37 +985,40 @@ private:
}
void realloc_concat(char*& first, size_t& first_size,
const char* const second, size_t second_size) {
// TODO make buffer_size an argument
next_line_buffer_size_ = first_size + second_size + 3;
auto new_first = static_cast<char*>(
realloc(static_cast<void*>(first), next_line_buffer_size_));
if (!first) {
throw std::bad_alloc{};
}
size_t& buffer_size, const char* const second,
size_t second_size) {
buffer_size = first_size + second_size + 3;
auto* new_first = static_cast<char*>(
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);
[[nodiscard]] bool append_next_line_to_buffer(char*& buffer,
size_t& line_size,
size_t buffer_size) {
undo_remove_eol(buffer, line_size, buffer_size);
ssize_t next_ssize =
get_line(&helper_buffer_, &helper_size_, file_);
if (next_ssize == -1) {
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 (eof) {
return false;
}
++line_number_;
size_t next_size = remove_eol(helper_buffer_, next_ssize);
realloc_concat(buffer, size, helper_buffer_, next_size);
realloc_concat(buffer, line_size, next_line_buffer_size_,
helper_buffer_, next_size);
return true;
}
std::string get_buffer() {
return std::string{next_line_buffer_, next_line_buffer_size_};
[[nodiscard]] std::string get_buffer() {
return std::string{next_line_buffer_, next_line_size_};
}
////////////////
@ -879,13 +1033,18 @@ private:
size_t buffer_size_{0};
size_t next_line_buffer_size_{0};
size_t helper_size_{0};
size_t helper_buffer_size{0};
std::string delim_;
FILE* file_{nullptr};
const char* csv_data_buffer_{nullptr};
size_t csv_data_size_{0};
size_t curr_char_{0};
bool crlf_{false};
size_t line_number_{0};
size_t chars_read_{0};
size_t next_line_size_{0};
};
@ -902,4 +1061,4 @@ private:
bool eof_{false};
};
} /* ss */
} /* namespace ss */

View File

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

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
@ -293,4 +293,7 @@ private:
template <typename... Options>
struct setup<setup<Options...>> : setup<Options...> {};
} /* ss */
template <typename... Options>
struct setup<std::tuple<Options...>> : setup<Options...> {};
} /* namespace ss */

View File

@ -2,11 +2,9 @@
#include "common.hpp"
#include "exception.hpp"
#include "setup.hpp"
#include "type_traits.hpp"
#include <algorithm>
#include <cstdlib>
#include <cstring>
#include <memory>
#include <string>
#include <vector>
@ -30,7 +28,7 @@ private:
public:
using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>;
bool valid() const {
[[nodiscard]] bool valid() const {
if constexpr (string_error) {
return error_.empty();
} else if constexpr (throw_on_error) {
@ -40,12 +38,12 @@ public:
}
}
const std::string& error_msg() const {
[[nodiscard]] const std::string& error_msg() const {
assert_string_error_defined<string_error>();
return error_;
}
bool unterminated_quote() const {
[[nodiscard]] bool unterminated_quote() const {
return unterminated_quote_;
}
@ -57,13 +55,21 @@ public:
return split_impl_select_delim(delimiter);
}
[[nodiscard]] const split_data& get_split_data() const {
return split_data_;
}
void clear_split_data() {
split_data_.clear();
}
private:
////////////////
// resplit
////////////////
// number of characters the end of line is shifted backwards
size_t size_shifted() const {
[[nodiscard]] size_t size_shifted() const {
return escaped_;
}
@ -86,7 +92,7 @@ private:
}
const auto [old_line, old_begin] = *std::prev(split_data_.end());
size_t begin = old_begin - old_line - 1;
const size_t begin = old_begin - old_line - 1;
// safety measure
if (new_size != -1 && static_cast<size_t>(new_size) < begin) {
@ -194,19 +200,19 @@ private:
// matching
////////////////
bool match(const char* const curr, char delim) {
[[nodiscard]] bool match(const char* const curr, char delim) {
return *curr == delim;
};
bool match(const char* const curr, const std::string& delim) {
return strncmp(curr, delim.c_str(), delim.size()) == 0;
[[nodiscard]] bool match(const char* const curr, const std::string& delim) {
return std::strncmp(curr, delim.c_str(), delim.size()) == 0;
};
size_t delimiter_size(char) {
[[nodiscard]] size_t delimiter_size(char) {
return 1;
}
size_t delimiter_size(const std::string& delim) {
[[nodiscard]] size_t delimiter_size(const std::string& delim) {
return delim.size();
}
@ -227,7 +233,7 @@ private:
}
template <typename Delim>
std::tuple<size_t, bool> match_delimiter(line_ptr_type begin,
[[nodiscard]] std::tuple<size_t, bool> match_delimiter(line_ptr_type begin,
const Delim& delim) {
line_ptr_type end = begin;
@ -322,8 +328,9 @@ private:
trim_left_if_enabled(begin_);
for (done_ = false; !done_; read(delim))
;
for (done_ = false; !done_;) {
read(delim);
}
return split_data_;
}
@ -462,7 +469,6 @@ private:
// members
////////////////
public:
error_type error_{};
bool unterminated_quote_{false};
bool done_{true};
@ -479,4 +485,4 @@ public:
friend class converter;
};
} /* ss */
} /* namespace ss */

View File

@ -34,7 +34,11 @@ struct left_of_impl;
template <size_t N, typename T, typename... Ts>
struct left_of_impl {
static_assert(N < 128, "recursion limit reached");
private:
constexpr static auto recursion_limit = 128;
public:
static_assert(N < recursion_limit, "recursion limit reached");
static_assert(N != 0, "cannot take the whole tuple");
using type = tup_cat_t<T, typename left_of_impl<N - 1, Ts...>::type>;
};
@ -362,12 +366,12 @@ constexpr bool is_instance_of_v = is_instance_of<Template, Ts...>::value;
////////////////
template <class T, std::size_t... Is, class U>
T to_object_impl(std::index_sequence<Is...>, U&& data) {
[[nodiscard]] T to_object_impl(std::index_sequence<Is...>, U&& data) {
return {std::get<Is>(std::forward<U>(data))...};
}
template <class T, class U>
T to_object(U&& data) {
[[nodiscard]] T to_object(U&& data) {
using NoRefU = std::decay_t<U>;
if constexpr (is_instance_of_v<std::tuple, NoRefU>) {
return to_object_impl<
@ -378,4 +382,4 @@ T to_object(U&& data) {
}
}
} /* trait */
} /* namespace ss */

View File

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

6
script/ci_install_lcov.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env sh
echo yes | cpan DateTime Capture::Tiny
wget -qO- https://github.com/linux-test-project/lcov/releases/download/v2.0/lcov-2.0.tar.gz | tar xvz
(cd lcov-2.0 && make install)

View File

@ -14,14 +14,21 @@ headers = ['type_traits.hpp',
combined_file = []
includes = []
in_pp_block = False
for header in headers:
with open(headers_dir + header) as f:
for line in f.read().splitlines():
if '#if ' in line:
in_pp_block = True
if '#endif' in line:
in_pp_block = False
if '#include "' in line or '#include <fast_float' in line:
continue
if '#include <' in line:
if '#include <' in line and not in_pp_block:
includes.append(line)
continue
@ -30,6 +37,7 @@ for header in headers:
includes = sorted(set(includes))
print('#pragma once')
print('\n'.join(includes))
print('#define SSP_DISABLE_FAST_FLOAT')
print('\n'.join(combined_file))

843
ssp.hpp

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -2,7 +2,11 @@ doctest_dep = dependency('doctest')
add_project_arguments('-DDOCTEST_CONFIG_IMPLEMENT_WITH_MAIN', language: 'cpp')
tests = [
'parser',
'parser1_1',
'parser1_2',
'parser1_3',
'parser1_4',
'parser1_5',
'splitter',
'converter',
'extractions',
@ -10,6 +14,8 @@ tests = [
'parser2_2',
'parser2_3',
'parser2_4',
'parser2_5',
'parser2_6',
'extractions_without_fast_float',
]

View File

@ -1,5 +1,4 @@
#include "test_helpers.hpp"
#include <algorithm>
#include <ss/converter.hpp>
TEST_CASE("converter test split") {
@ -11,7 +10,8 @@ TEST_CASE("converter test split") {
{" x x x x | x ", {" x x x x ", " x "}, "|"},
{"a::b::c::d", {"a", "b", "c", "d"}, "::"},
{"x\t-\ty", {"x", "y"}, "\t-\t"},
{"x", {"x"}, ","}} // clang-format on
{"x", {"x"}, ","}}
// clang-format on
) {
auto split = c.split(s, delim);
CHECK_EQ(split.size(), expected.size());
@ -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,118 @@ 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>("");
std::ignore = c.convert<T>("");
REQUIRE_FALSE(c.valid());
c.convert<int>("1", "");
std::ignore = c.convert<T>("1", "");
REQUIRE_FALSE(c.valid());
c.convert<int>("10", "");
std::ignore = c.convert<T>("10", "");
REQUIRE_FALSE(c.valid());
c.convert<int, void>("");
std::ignore = c.convert<T, void>("");
REQUIRE_FALSE(c.valid());
c.convert<int, void>(",junk");
std::ignore = c.convert<T, void>(",junk");
REQUIRE_FALSE(c.valid());
c.convert<void, int>("junk,");
std::ignore = c.convert<void, T>("junk,");
REQUIRE_FALSE(c.valid());
c.convert<int>("x");
std::ignore = c.convert<T>("x");
REQUIRE_FALSE(c.valid());
c.convert<int, void>("x");
std::ignore = c.convert<T, void>("x");
REQUIRE_FALSE(c.valid());
c.convert<int, void>("x,junk");
std::ignore = c.convert<T, void>("x,junk");
REQUIRE_FALSE(c.valid());
c.convert<void, int>("junk,x");
std::ignore = c.convert<void, T>("junk,x");
REQUIRE_FALSE(c.valid());
c.convert<void, std::variant<int, double>, double>("junk;.5.5;6", ";");
std::ignore =
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(std::ignore = c.convert<T>(""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("1", ""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("10", ""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>(""));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>(",junk"));
REQUIRE_EXCEPTION(std::ignore = c.convert<void, T>("junk,"));
REQUIRE_EXCEPTION(std::ignore = c.convert<T>("x"));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>("x"));
REQUIRE_EXCEPTION(std::ignore = c.convert<T, void>("x,junk"));
REQUIRE_EXCEPTION(std::ignore = c.convert<void, T>("junk,x"));
REQUIRE_EXCEPTION(
c.convert<void, std::variant<int, double>, double>("junk;.5.5;6", ";"));
std::ignore =
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");
std::ignore = c.convert<ss::ax<T, 0>>("0");
REQUIRE_FALSE(c.valid());
c.convert<ss::ax<int, 0, 1, 2>>("1");
std::ignore = c.convert<ss::ax<T, 0, 1, 2>>("1");
REQUIRE_FALSE(c.valid());
c.convert<void, char, ss::ax<int, 0, 1, 2>>("junk,c,1");
std::ignore = c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1");
REQUIRE_FALSE(c.valid());
c.convert<ss::ax<int, 1>, char>("1,c");
std::ignore = 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(std::ignore = c.convert<ss::ax<T, 0>>("0"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 0, 1, 2>>("1"));
REQUIRE_EXCEPTION(
std::ignore = c.convert<void, char, ss::ax<T, 0, 1, 2>>("junk,c,1"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ax<T, 1>, char>("1,c"));
try {
{
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) {
@ -392,13 +397,13 @@ TEST_CASE("converter test ss:ax restriction (all except) with exceptions") {
TEST_CASE("converter test ss:nx restriction (none except)") {
ss::converter c;
c.convert<ss::nx<int, 1>>("3");
std::ignore = c.convert<ss::nx<int, 1>>("3");
REQUIRE_FALSE(c.valid());
c.convert<char, ss::nx<int, 1, 2, 69>>("c,3");
std::ignore = c.convert<char, ss::nx<int, 1, 2, 69>>("c,3");
REQUIRE_FALSE(c.valid());
c.convert<ss::nx<int, 1>, char>("3,c");
std::ignore = c.convert<ss::nx<int, 1>, char>("3,c");
REQUIRE_FALSE(c.valid());
{
@ -426,9 +431,10 @@ TEST_CASE("converter test ss:nx restriction (none except)") {
TEST_CASE("converter test ss:nx restriction (none except) with exceptions") {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(c.convert<ss::nx<int, 1>>("3"));
REQUIRE_EXCEPTION(c.convert<char, ss::nx<int, 1, 2, 69>>("c,3"));
REQUIRE_EXCEPTION(c.convert<ss::nx<int, 1>, char>("3,c"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::nx<int, 1>>("3"));
REQUIRE_EXCEPTION(std::ignore =
c.convert<char, ss::nx<int, 1, 2, 69>>("c,3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::nx<int, 1>, char>("3,c"));
try {
{
@ -456,65 +462,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");
std::ignore = c.convert<ss::ir<T, 0, 2>>("3");
REQUIRE_FALSE(c.valid());
c.convert<char, ss::ir<int, 4, 69>>("c,3");
std::ignore = c.convert<char, ss::ir<T, 4, 69>>("c,3");
REQUIRE_FALSE(c.valid());
c.convert<ss::ir<int, 1, 2>, char>("3,c");
std::ignore = 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(std::ignore = c.convert<ss::ir<T, 0, 2>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<char, ss::ir<T, 4, 69>>("c,3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ir<T, 1, 2>, char>("3,c"));
try {
{
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'));
}
@ -526,16 +535,16 @@ TEST_CASE("converter test ss:ir restriction (in range) with exceptions") {
TEST_CASE("converter test ss:oor restriction (out of range)") {
ss::converter c;
c.convert<ss::oor<int, 1, 5>>("3");
std::ignore = c.convert<ss::oor<int, 1, 5>>("3");
REQUIRE_FALSE(c.valid());
c.convert<ss::oor<int, 0, 2>>("2");
std::ignore = c.convert<ss::oor<int, 0, 2>>("2");
REQUIRE_FALSE(c.valid());
c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk");
std::ignore = c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk");
REQUIRE_FALSE(c.valid());
c.convert<ss::oor<int, 1, 20>, char>("1,c");
std::ignore = c.convert<ss::oor<int, 1, 20>, char>("1,c");
REQUIRE_FALSE(c.valid());
{
@ -560,10 +569,12 @@ TEST_CASE("converter test ss:oor restriction (out of range)") {
TEST_CASE("converter test ss:oor restriction (out of range) with exceptions") {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(c.convert<ss::oor<int, 1, 5>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::oor<int, 0, 2>>("2"));
REQUIRE_EXCEPTION(c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk"));
REQUIRE_EXCEPTION(c.convert<ss::oor<int, 1, 20>, char>("1,c"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::oor<int, 1, 5>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::oor<int, 0, 2>>("2"));
REQUIRE_EXCEPTION(
std::ignore = c.convert<char, ss::oor<int, 0, 1>, void>("c,1,junk"));
REQUIRE_EXCEPTION(std::ignore =
c.convert<ss::oor<int, 1, 20>, char>("1,c"));
try {
{
@ -604,19 +615,19 @@ inline bool ss::extract(const char* begin, const char* end,
TEST_CASE("converter test ss:ne restriction (not empty)") {
ss::converter c;
c.convert<ss::ne<std::string>>("");
std::ignore = c.convert<ss::ne<std::string>>("");
REQUIRE_FALSE(c.valid());
c.convert<int, ss::ne<std::string>>("3,");
std::ignore = c.convert<int, ss::ne<std::string>>("3,");
REQUIRE_FALSE(c.valid());
c.convert<ss::ne<std::string>, int>(",3");
std::ignore = c.convert<ss::ne<std::string>, int>(",3");
REQUIRE_FALSE(c.valid());
c.convert<void, ss::ne<std::string>, int>("junk,,3");
std::ignore = c.convert<void, ss::ne<std::string>, int>("junk,,3");
REQUIRE_FALSE(c.valid());
c.convert<ss::ne<std::vector<int>>>("");
std::ignore = c.convert<ss::ne<std::vector<int>>>("");
REQUIRE_FALSE(c.valid());
{
@ -639,11 +650,12 @@ TEST_CASE("converter test ss:ne restriction (not empty)") {
TEST_CASE("converter test ss:ne restriction (not empty) with exceptions") {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(c.convert<ss::ne<std::string>>(""));
REQUIRE_EXCEPTION(c.convert<int, ss::ne<std::string>>("3,"));
REQUIRE_EXCEPTION(c.convert<ss::ne<std::string>, int>(",3"));
REQUIRE_EXCEPTION(c.convert<void, ss::ne<std::string>, int>("junk,,3"));
REQUIRE_EXCEPTION(c.convert<ss::ne<std::vector<int>>>(""));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::string>>(""));
REQUIRE_EXCEPTION(std::ignore = c.convert<int, ss::ne<std::string>>("3,"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::string>, int>(",3"));
REQUIRE_EXCEPTION(std::ignore =
c.convert<void, ss::ne<std::string>, int>("junk,,3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::ne<std::vector<int>>>(""));
try {
{
@ -671,22 +683,22 @@ TEST_CASE(
"converter test ss:lt ss::lte ss::gt ss::gte restriction (in range)") {
ss::converter c;
c.convert<ss::lt<int, 3>>("3");
std::ignore = c.convert<ss::lt<int, 3>>("3");
REQUIRE_FALSE(c.valid());
c.convert<ss::lt<int, 2>>("3");
std::ignore = c.convert<ss::lt<int, 2>>("3");
REQUIRE_FALSE(c.valid());
c.convert<ss::gt<int, 3>>("3");
std::ignore = c.convert<ss::gt<int, 3>>("3");
REQUIRE_FALSE(c.valid());
c.convert<ss::gt<int, 4>>("3");
std::ignore = c.convert<ss::gt<int, 4>>("3");
REQUIRE_FALSE(c.valid());
c.convert<ss::lte<int, 2>>("3");
std::ignore = c.convert<ss::lte<int, 2>>("3");
REQUIRE_FALSE(c.valid());
c.convert<ss::gte<int, 4>>("3");
std::ignore = c.convert<ss::gte<int, 4>>("3");
REQUIRE_FALSE(c.valid());
{
@ -730,12 +742,12 @@ TEST_CASE("converter test ss:lt ss::lte ss::gt ss::gte restriction (in range) "
"with exception") {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(c.convert<ss::lt<int, 3>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::lt<int, 2>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::gt<int, 3>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::gt<int, 4>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::lte<int, 2>>("3"));
REQUIRE_EXCEPTION(c.convert<ss::gte<int, 4>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lt<int, 3>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lt<int, 2>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gt<int, 3>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gt<int, 4>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::lte<int, 2>>("3"));
REQUIRE_EXCEPTION(std::ignore = c.convert<ss::gte<int, 4>>("3"));
try {
{
@ -780,14 +792,14 @@ TEST_CASE("converter test ss:lt ss::lte ss::gt ss::gte restriction (in range) "
TEST_CASE("converter test error mode") {
ss::converter<ss::string_error> c;
c.convert<int>("junk");
std::ignore = c.convert<int>("junk");
CHECK_FALSE(c.valid());
CHECK_FALSE(c.error_msg().empty());
}
TEST_CASE("converter test throw on error mode") {
ss::converter<ss::throw_on_error> c;
REQUIRE_EXCEPTION(c.convert<int>("junk"));
REQUIRE_EXCEPTION(std::ignore = c.convert<int>("junk"));
}
TEST_CASE("converter test converter with quotes spacing and escaping") {
@ -904,7 +916,7 @@ TEST_CASE("converter test invalid split conversions") {
{
// mismatched quote
c.convert<std::string, std::string, double, char>(
std::ignore = c.convert<std::string, std::string, double, char>(
buff(R"( "just , some , "12.3","a" )"));
CHECK_FALSE(c.valid());
CHECK_FALSE(c.unterminated_quote());
@ -913,7 +925,7 @@ TEST_CASE("converter test invalid split conversions") {
{
// unterminated quote
c.convert<std::string, std::string, double, std::string>(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)"));
CHECK_FALSE(c.valid());
CHECK(c.unterminated_quote());
@ -922,7 +934,7 @@ TEST_CASE("converter test invalid split conversions") {
{
// unterminated escape
c.convert<std::string, std::string, double, std::string>(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,strings\)"));
CHECK_FALSE(c.valid());
CHECK_FALSE(c.unterminated_quote());
@ -931,7 +943,7 @@ TEST_CASE("converter test invalid split conversions") {
{
// unterminated escape while quoting
c.convert<std::string, std::string, double, std::string>(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\)"));
CHECK_FALSE(c.valid());
CHECK_FALSE(c.unterminated_quote());
@ -940,7 +952,7 @@ TEST_CASE("converter test invalid split conversions") {
{
// unterminated escaped quote
c.convert<std::string, std::string, double, std::string>(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\")"));
CHECK_FALSE(c.valid());
CHECK(c.unterminated_quote());
@ -954,28 +966,32 @@ TEST_CASE("converter test invalid split conversions with exceptions") {
c;
// mismatched quote
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, char>(
REQUIRE_EXCEPTION(std::ignore =
c.convert<std::string, std::string, double, char>(
buff(R"( "just , some , "12.3","a" )")));
CHECK_FALSE(c.unterminated_quote());
// unterminated quote
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
REQUIRE_EXCEPTION(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"( ju\,st , "so,me" , 12.34 , "str""ings)")));
CHECK(c.unterminated_quote());
// unterminated escape
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
REQUIRE_EXCEPTION(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,strings\)")));
CHECK_FALSE(c.unterminated_quote());
// unterminated escape while quoting
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
REQUIRE_EXCEPTION(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\)")));
CHECK_FALSE(c.unterminated_quote());
// unterminated escaped quote
REQUIRE_EXCEPTION(c.convert<std::string, std::string, double, std::string>(
REQUIRE_EXCEPTION(
std::ignore = c.convert<std::string, std::string, double, std::string>(
buff(R"(just,some,2,"strings\")")));
CHECK(c.unterminated_quote());
}

View File

@ -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 {};
} /* anonymous 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)); \
} \
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"},
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<int> v;
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,5 +1,4 @@
#include "test_helpers.hpp"
#include <algorithm>
#define SSP_DISABLE_FAST_FLOAT
#include <ss/extract.hpp>

View File

@ -1,7 +1,11 @@
#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>
@ -12,7 +16,30 @@
#include <doctest.h>
#endif
namespace ss {
template <typename... Ts>
class parser;
} /* namespace 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_;
@ -34,27 +61,34 @@ struct buffer {
[[maybe_unused]] inline buffer buff;
std::string time_now_rand() {
[[maybe_unused]] std::string time_now_rand() {
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());
}
struct unique_file_name {
static inline int i = 0;
const std::string name;
std::string name;
unique_file_name(const std::string& test)
: name{"random_" + test + "_" + std::to_string(i++) + "_" +
time_now_rand() + "_file.csv"} {
unique_file_name(const std::string& test) {
do {
name = "ssp_test_" + test + "_" + std::to_string(i++) + "_" +
time_now_rand() + "_file.csv";
} while (std::filesystem::exists(name));
}
~unique_file_name() {
try {
std::filesystem::remove(name);
} catch (const std::filesystem::filesystem_error& e) {
std::cerr << e.what() << std::endl;
}
}
};
@ -111,9 +145,20 @@ struct unique_file_name {
CHECK_FALSE(std::string{e.what()}.empty()); \
}
#define CHECK_EQ_ARRAY(first, second) \
{ \
const auto& first_ = (first); \
const auto& second_ = (second); \
CHECK_EQ(first_.size(), second_.size()); \
for (size_t i_ = 0; i_ < std::min(first_.size(), second_.size()); \
++i_) { \
CHECK_EQ(first_[i_], second_[i_]); \
} \
}
template <typename T>
std::vector<std::vector<T>> vector_combinations(const std::vector<T>& v,
size_t n) {
[[maybe_unused]] std::vector<std::vector<T>> vector_combinations(
const std::vector<T>& v, size_t n) {
std::vector<std::vector<T>> ret;
if (n <= 1) {
for (const auto& i : v) {
@ -126,9 +171,85 @@ std::vector<std::vector<T>> vector_combinations(const std::vector<T>& v,
for (const auto& i : v) {
for (auto j : inner_combinations) {
j.insert(j.begin(), i);
ret.push_back(move(j));
ret.push_back(std::move(j));
}
}
return ret;
}
} /* namespace */
[[maybe_unused]] std::string merge_header(
const std::vector<std::string>& header,
const std::string& delimiter = ss::default_delimiter) {
std::string s;
if (!header.empty()) {
for (const auto& i : header) {
s.append(i);
s.append(delimiter);
}
for (size_t i = 0; i < delimiter.size(); ++i) {
s.pop_back();
}
}
return s;
};
[[maybe_unused]] std::string make_buffer(const std::string& file_name) {
std::ifstream in{file_name, std::ios::binary};
std::string tmp;
std::string out;
auto copy_if_whitespaces = [&] {
std::string matches = "\n\r\t ";
while (std::any_of(matches.begin(), matches.end(),
[&](auto c) { return in.peek() == c; })) {
if (in.peek() == '\r') {
out += "\r\n";
in.ignore(2);
} else {
out += std::string{static_cast<char>(in.peek())};
in.ignore(1);
}
}
};
// Evade small string optimization
out.reserve(sizeof(out) + 1);
copy_if_whitespaces();
while (in >> tmp) {
out += tmp;
copy_if_whitespaces();
}
return out;
}
template <bool buffer_mode, typename... Ts>
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);
return {ss::parser<Ts...>{buffer.data(), buffer.size(), delim},
std::move(buffer)};
} else {
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);
}
} /* anonymous namespace */

File diff suppressed because it is too large Load Diff

116
test/test_parser1.hpp Normal file
View File

@ -0,0 +1,116 @@
#pragma once
#include "test_helpers.hpp"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <ss/parser.hpp>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
namespace {
#ifdef _WIN32
void replace_all(std::string& s, const std::string& from,
const std::string& to) {
if (from.empty()) return;
size_t start_pos = 0;
while ((start_pos = s.find(from, start_pos)) != std::string::npos) {
s.replace(start_pos, from.length(), to);
start_pos += to.length();
}
}
#endif
template <typename... Ts>
void expect_error_on_command(ss::parser<Ts...>& p,
const std::function<void()> command) {
if (ss::setup<Ts...>::throw_on_error) {
try {
command();
FAIL("expected exception");
} catch (const std::exception& e) {
CHECK_FALSE(std::string{e.what()}.empty());
}
} else {
command();
CHECK(!p.valid());
if constexpr (ss::setup<Ts...>::string_error) {
CHECK_FALSE(p.error_msg().empty());
}
}
}
[[maybe_unused]] void update_if_crlf(std::string& s) {
#ifdef _WIN32
replace_all(s, "\r\n", "\n");
#else
(void)(s);
#endif
}
struct X {
constexpr static auto delim = ",";
constexpr static auto empty = "_EMPTY_";
int i;
double d;
std::string s;
[[nodiscard]] std::string to_string() const {
if (s == empty) {
return "";
}
return std::to_string(i)
.append(delim)
.append(std::to_string(d))
.append(delim)
.append(s);
}
[[nodiscard]] auto tied() const {
return std::tie(i, d, s);
}
};
template <typename T>
[[nodiscard]] std::enable_if_t<ss::has_m_tied_t<T>, bool> operator==(
const T& lhs, const T& rhs) {
return lhs.tied() == rhs.tied();
}
template <typename T>
static void make_and_write(const std::string& file_name,
const std::vector<T>& data,
const std::vector<std::string>& header = {},
bool new_line_eof = true) {
std::ofstream out{file_name};
#ifdef _WIN32
std::vector<const char*> new_lines = {"\n"};
#else
std::vector<const char*> new_lines = {"\n", "\r\n"};
#endif
for (const auto& i : header) {
if (&i != &header.front()) {
out << T::delim;
}
out << i;
}
if (!header.empty()) {
out << new_lines.front();
}
for (size_t i = 0; i < data.size(); ++i) {
out << data[i].to_string();
if (new_line_eof || i + 1 < data.size()) {
out << new_lines[i % new_lines.size()];
}
}
}
} /* anonymous namespace */

675
test/test_parser1_1.cpp Normal file
View File

@ -0,0 +1,675 @@
#include "test_parser1.hpp"
TEST_CASE("test file not found") {
unique_file_name f{"file_not_found"};
{
ss::parser p{f.name, ","};
CHECK_FALSE(p.valid());
}
{
ss::parser<ss::string_error> p{f.name, ","};
CHECK_FALSE(p.valid());
CHECK_FALSE(p.error_msg().empty());
}
try {
ss::parser<ss::throw_on_error> p{f.name, ","};
FAIL("Expected exception...");
} catch (const std::exception& e) {
CHECK_FALSE(std::string{e.what()}.empty());
}
}
TEST_CASE("test null buffer") {
{
ss::parser p{nullptr, 10, ","};
CHECK_FALSE(p.valid());
}
{
ss::parser<ss::string_error> p{nullptr, 10, ","};
CHECK_FALSE(p.valid());
CHECK_FALSE(p.error_msg().empty());
}
try {
ss::parser<ss::throw_on_error> p{nullptr, 10, ","};
FAIL("Expected exception...");
} catch (const std::exception& e) {
CHECK_FALSE(std::string{e.what()}.empty());
}
}
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);
}
[[nodiscard]] 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()) {
std::ignore =
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, ErrorMode>(f.name, ",");
ss::parser p0{std::move(p)};
p = std::move(p0);
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
auto move_rotate = [&p = p, &p0 = p0] {
auto p1 = std::move(p);
p0 = std::move(p1);
p = std::move(p0);
};
while (!p.eof()) {
move_rotate();
auto a = p.template get_next<int, double, std::string>();
i.emplace_back(ss::to_object<X>(a));
}
for (const auto& a : p2.template iterate<int, double, std::string>()) {
i2.emplace_back(ss::to_object<X>(a));
}
CHECK_EQ(i, data);
CHECK_EQ(i2, data);
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
auto [p3, ___] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i3;
std::vector<X> expected = {std::begin(data) + 1, std::end(data)};
using tup = std::tuple<int, double, std::string>;
p.ignore_next();
while (!p.eof()) {
auto a = p.template get_next<tup>();
i.emplace_back(ss::to_object<X>(a));
}
p2.ignore_next();
for (const auto& a : p2.template iterate<tup>()) {
i2.emplace_back(ss::to_object<X>(a));
}
p3.ignore_next();
for (auto it = p3.template iterate<tup>().begin();
it != p3.template iterate<tup>().end(); ++it) {
i3.emplace_back(ss::to_object<X>(*it));
}
CHECK_EQ(i, expected);
CHECK_EQ(i2, expected);
CHECK_EQ(i3, expected);
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
while (!p.eof()) {
i.push_back(p.template get_object<X, int, double, std::string>());
}
for (auto&& a :
p2.template iterate_object<X, int, double, std::string>()) {
i2.push_back(std::move(a));
}
CHECK_EQ(i, data);
CHECK_EQ(i2, data);
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
for (auto&& a :
p.template iterate_object<X, int, double, std::string>()) {
i.push_back(std::move(a));
}
CHECK_EQ(i, data);
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
using tup = std::tuple<int, double, std::string>;
while (!p.eof()) {
i.push_back(p.template get_object<X, tup>());
}
for (auto it = p2.template iterate_object<X, tup>().begin();
it != p2.template iterate_object<X, tup>().end(); it++) {
i2.push_back({it->i, it->d, it->s});
}
CHECK_EQ(i, data);
CHECK_EQ(i2, data);
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
using tup = std::tuple<int, double, std::string>;
for (auto&& a : p.template iterate_object<X, tup>()) {
i.push_back(std::move(a));
}
CHECK_EQ(i, data);
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
while (!p.eof()) {
i.push_back(p.template get_next<X>());
}
CHECK_EQ(i, data);
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
for (auto&& a : p.template iterate<X>()) {
i.push_back(std::move(a));
}
CHECK_EQ(i, data);
}
{
constexpr int excluded = 3;
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
while (!p.eof()) {
try {
auto a = p.template get_object<X, ss::ax<int, excluded>, double,
std::string>();
if (p.valid()) {
i.push_back(a);
}
} catch (...) {
// ignore
};
}
if (!T::ThrowOnError) {
for (auto&& a : p2.template iterate_object<X, ss::ax<int, excluded>,
double, std::string>()) {
if (p2.valid()) {
i2.push_back(std::move(a));
}
}
}
std::vector<X> expected;
for (auto& x : data) {
if (x.i != excluded) {
expected.push_back(x);
}
}
std::copy_if(data.begin(), data.end(), expected.begin(),
[&](const X& x) { return x.i != excluded; });
CHECK_EQ(i, expected);
if (!T::ThrowOnError) {
CHECK_EQ(i2, expected);
}
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
std::vector<X> i2;
while (!p.eof()) {
try {
auto a = p.template get_object<X, ss::nx<int, 3>, double,
std::string>();
if (p.valid()) {
i.push_back(a);
}
} catch (...) {
// ignore
}
}
if (!T::ThrowOnError) {
for (auto&& a : p2.template iterate_object<X, ss::nx<int, 3>,
double, std::string>()) {
if (p2.valid()) {
i2.push_back(std::move(a));
}
}
}
std::vector<X> expected = {{3, 4, "y"}};
CHECK_EQ(i, expected);
if (!T::ThrowOnError) {
CHECK_EQ(i2, expected);
}
}
{
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, ErrorMode>(empty_f.name, ",");
std::vector<X> i;
auto [p2, __] = make_parser<buffer_mode, ErrorMode>(empty_f.name, ",");
std::vector<X> i2;
while (!p.eof()) {
i.push_back(p.template get_next<X>());
}
for (auto&& a : p2.template iterate<X>()) {
i2.push_back(std::move(a));
}
CHECK(i.empty());
CHECK(i2.empty());
}
}
using test_tuple = std::tuple<double, char, double>;
struct test_struct {
int i;
double d;
char c;
auto tied() {
return std::tie(i, d, c);
}
};
static inline void expect_test_struct(const test_struct&) {
}
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 :
{"10,a,11.1", "10,20,11.1", "junk", "10,11.1", "1,11.1,a", "junk",
"10,junk", "11,junk", "10,11.1,c", "10,20", "10,22.2,f"}) {
out << i << std::endl;
}
}
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 = [] {};
REQUIRE(p.valid());
REQUIRE_FALSE(p.eof());
{
constexpr static auto expectedData = std::tuple{10, 'a', 11.1};
auto [d1, d2, d3, d4] =
p.template try_next<int, int, double>(fail)
.template or_else<test_struct>(fail)
.template or_else<int, char, double>(
[&](auto&& data) { CHECK_EQ(data, expectedData); })
.on_error(fail)
.template or_else<test_tuple>(fail)
.values();
REQUIRE(p.valid());
REQUIRE_FALSE(d1);
REQUIRE_FALSE(d2);
REQUIRE(d3);
REQUIRE_FALSE(d4);
CHECK_EQ(*d3, expectedData);
}
{
REQUIRE(!p.eof());
constexpr static auto expectedData = std::tuple{10, 20, 11.1};
auto [d1, d2, d3, d4] =
p.template try_next<int, int, double>(
[&](auto& i1, auto i2, double d) {
CHECK_EQ(std::tie(i1, i2, d), expectedData);
})
.on_error(fail)
.template or_object<test_struct, int, double, char>(fail)
.on_error(fail)
.template or_else<test_tuple>(fail)
.on_error(fail)
.template or_else<int, char, double>(fail)
.values();
REQUIRE(p.valid());
REQUIRE(d1);
REQUIRE_FALSE(d2);
REQUIRE_FALSE(d3);
REQUIRE_FALSE(d4);
CHECK_EQ(*d1, expectedData);
}
{
REQUIRE(!p.eof());
auto [d1, d2, d3, d4, d5] =
p.template try_object<test_struct, int, double, char>(fail)
.on_error(expect_error)
.template or_else<int, char, char>(fail)
.template or_else<test_struct>(fail)
.template or_else<test_tuple>(fail)
.template or_else<int, char, double>(fail)
.values();
REQUIRE_FALSE(p.valid());
REQUIRE_FALSE(d1);
REQUIRE_FALSE(d2);
REQUIRE_FALSE(d3);
REQUIRE_FALSE(d4);
REQUIRE_FALSE(d5);
}
{
REQUIRE(!p.eof());
auto [d1, d2] =
p.template try_next<int, double>([](auto& i, auto& d) {
REQUIRE_EQ(std::tie(i, d), std::tuple{10, 11.1});
})
.template or_else<int, double>([](auto&, auto&) { FAIL(""); })
.values();
REQUIRE(p.valid());
REQUIRE(d1);
REQUIRE_FALSE(d2);
}
{
REQUIRE(!p.eof());
auto [d1, d2] =
p.template try_next<int, double>([](auto&, auto&) { FAIL(""); })
.template or_else<test_struct>(expect_test_struct)
.values();
REQUIRE(p.valid());
REQUIRE_FALSE(d1);
REQUIRE(d2);
CHECK_EQ(d2->tied(), std::tuple{1, 11.1, 'a'});
}
{
REQUIRE(!p.eof());
auto [d1, d2, d3, d4, d5] =
p.template try_next<int, int, double>(fail)
.template or_object<test_struct, int, double, char>()
.template or_else<test_struct>(expect_test_struct)
.template or_else<test_tuple>(fail)
.template or_else<std::tuple<int, double>>(fail)
.on_error(ignore_error)
.on_error(expect_error)
.values();
REQUIRE_FALSE(p.valid());
REQUIRE_FALSE(d1);
REQUIRE_FALSE(d2);
REQUIRE_FALSE(d3);
REQUIRE_FALSE(d4);
REQUIRE_FALSE(d5);
}
{
REQUIRE(!p.eof());
auto [d1, d2] =
p.template try_next<int, std::optional<int>>()
.on_error(ignore_error)
.on_error(fail)
.template or_else<std::tuple<int, std::string>>(fail)
.on_error(ignore_error)
.on_error(fail)
.on_error(ignore_error)
.values();
REQUIRE(p.valid());
REQUIRE(d1);
REQUIRE_FALSE(d2);
CHECK_EQ(*d1, std::tuple{10, std::nullopt});
}
{
REQUIRE_FALSE(p.eof());
auto [d1, d2] =
p.template try_next<int, std::variant<int, std::string>>()
.on_error(fail)
.template or_else<std::tuple<int, std::string>>(fail)
.on_error(fail)
.values();
REQUIRE(p.valid());
REQUIRE(d1);
REQUIRE_FALSE(d2);
CHECK_EQ(*d1, std::tuple{11, std::variant<int, std::string>{"junk"}});
}
{
REQUIRE(!p.eof());
auto [d1, d2] = p.template try_object<test_struct, int, double, char>()
.template or_else<int>(fail)
.values();
REQUIRE(p.valid());
REQUIRE(d1);
REQUIRE_FALSE(d2);
CHECK_EQ(d1->tied(), std::tuple{10, 11.1, 'c'});
}
{
REQUIRE_FALSE(p.eof());
auto [d1, d2, d3, d4] =
p.template try_next<int, int>([] { return false; })
.template or_else<int, double>([](auto&) { return false; })
.template or_else<int, int>()
.template or_else<int, int>(fail)
.values();
REQUIRE(p.valid());
REQUIRE_FALSE(d1);
REQUIRE_FALSE(d2);
REQUIRE(d3);
REQUIRE_FALSE(d4);
CHECK_EQ(d3.value(), std::tuple{10, 20});
}
{
REQUIRE(!p.eof());
auto [d1, d2, d3, d4] =
p.template try_object<test_struct, int, double, char>(
[] { return false; })
.template or_else<int, double>([](auto&) { return false; })
.template or_object<test_struct, int, double, char>()
.template or_else<int, int>(fail)
.values();
REQUIRE(p.valid());
REQUIRE_FALSE(d1);
REQUIRE_FALSE(d2);
REQUIRE(d3);
REQUIRE_FALSE(d4);
CHECK_EQ(d3->tied(), std::tuple{10, 22.2, 'f'});
}
CHECK(p.eof());
}
template <bool buffer_mode, typename... Ts>
void test_no_new_line_at_eof_impl(const std::vector<X>& data) {
unique_file_name f{"no_new_line_at_eof"};
make_and_write(f.name, data, {}, false);
auto [p, _] = make_parser<buffer_mode, Ts...>(f.name);
std::vector<X> parsed_data;
for (const auto& el : p.template iterate<X>()) {
parsed_data.push_back(el);
}
CHECK_EQ(data, parsed_data);
}
template <bool buffer_mode, typename... Ts>
void test_no_new_line_at_eof() {
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_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>();
}

265
test/test_parser1_2.cpp Normal file
View File

@ -0,0 +1,265 @@
#include "test_parser1.hpp"
struct my_string {
char* data{nullptr};
my_string() = default;
~my_string() {
delete[] data;
}
// make sure no object is copied
my_string(const my_string&) = delete;
my_string& operator=(const my_string&) = delete;
my_string(my_string&& other) : data{other.data} {
other.data = nullptr;
}
my_string& operator=(my_string&& other) {
data = other.data;
return *this;
}
};
template <>
inline bool ss::extract(const char* begin, const char* end, my_string& s) {
size_t size = end - begin;
s.data = new char[size + 1];
strncpy(s.data, begin, size);
s.data[size] = '\0';
return true;
}
struct xyz {
my_string x;
my_string y;
my_string z;
auto tied() {
return std::tie(x, y, z);
}
};
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, ErrorMode>("", "");
std::ignore =
p.template try_next<my_string, my_string, my_string>()
.template or_else<my_string, my_string, my_string, my_string>(
[](auto&&) {})
.template or_else<my_string>([](auto&) {})
.template or_else<xyz>([](auto&&) {})
.template or_object<xyz, my_string, my_string, my_string>(
[](auto&&) {})
.template or_else<std::tuple<my_string, my_string, my_string>>(
[](auto&, auto&, auto&) {});
}
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<BufferMode::value, ss::string_error>(f.name, ",");
REQUIRE_FALSE(p.eof());
std::ignore = p.template get_next<int>();
CHECK_FALSE(p.valid());
CHECK_FALSE(p.error_msg().empty());
}
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<BufferMode::value, ss::throw_on_error>(f.name, ",");
REQUIRE_FALSE(p.eof());
try {
std::ignore = p.template get_next<int>();
FAIL("Expected exception...");
} catch (const std::exception& e) {
CHECK_FALSE(std::string{e.what()}.empty());
}
}
static inline std::string no_quote(const std::string& s) {
if (!s.empty() && s[0] == '"') {
return {std::next(begin(s)), std::prev(end(s))};
}
return s;
}
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\""},
{7, 8, "\"u\"\"\""},
{9, 10, "v"},
{11, 12, "\"w\n\""}};
for (auto& [_, __, s] : data) {
update_if_crlf(s);
}
make_and_write(f.name, data);
for (auto& [_, __, s] : data) {
s = no_quote(s);
if (s[0] == 'u') {
s = "u\"";
}
}
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::multiline,
ss::quote<'"'>>(f.name, ",");
std::vector<X> i;
while (!p.eof()) {
auto a = p.template get_next<int, double, std::string>();
i.emplace_back(ss::to_object<X>(a));
}
for (auto& [_, __, s] : i) {
update_if_crlf(s);
}
CHECK_EQ(i, data);
auto [p_no_multiline, __] =
make_parser<buffer_mode, ErrorMode, ss::quote<'"'>>(f.name, ",");
while (!p.eof()) {
auto command = [&p_no_multiline = p_no_multiline] {
std::ignore =
p_no_multiline.template get_next<int, double, std::string>();
};
expect_error_on_command(p_no_multiline, command);
}
}
static inline std::string no_escape(std::string& s) {
s.erase(std::remove(begin(s), end(s), '\\'), end(s));
return s;
}
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"},
{3, 4, "y\\\ny\\\ny"},
{9, 10, "v\\\\"},
{11, 12, "w\\\n"}};
for (auto& [_, __, s] : data) {
update_if_crlf(s);
}
make_and_write(f.name, data);
for (auto& [_, __, s] : data) {
s = no_escape(s);
if (s == "v") {
s = "v\\";
}
}
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::multiline,
ss::escape<'\\'>>(f.name, ",");
std::vector<X> i;
while (!p.eof()) {
auto a = p.template get_next<int, double, std::string>();
i.emplace_back(ss::to_object<X>(a));
}
for (auto& [_, __, s] : i) {
update_if_crlf(s);
}
CHECK_EQ(i, data);
auto [p_no_multiline, __] =
make_parser<buffer_mode, ErrorMode, ss::escape<'\\'>>(f.name, ",");
while (!p.eof()) {
auto command = [&p_no_multiline = p_no_multiline] {
auto a =
p_no_multiline.template get_next<int, double, std::string>();
};
expect_error_on_command(p_no_multiline, command);
}
}
TEST_CASE_TEMPLATE("test quote escape multiline", T, ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"quote_escape_multiline"};
{
std::ofstream out{f.name};
out << "1,2,\"just\\\n\nstrings\"" << std::endl;
#ifndef _WIN32
out << "3,4,\"just\r\nsome\\\r\n\n\\\nstrings\"" << std::endl;
out << "5,6,\"just\\\n\\\r\n\r\n\nstrings" << std::endl;
#else
out << "3,4,\"just\nsome\\\n\n\\\nstrings\"" << std::endl;
out << "5,6,\"just\\\n\\\n\n\nstrings" << std::endl;
#endif
out << "7,8,\"just strings\"" << std::endl;
out << "9,10,just strings" << std::endl;
}
size_t bad_lines = 1;
auto num_errors = 0;
auto [p, _] = make_parser<buffer_mode, ErrorMode, ss::multiline,
ss::escape<'\\'>, ss::quote<'"'>>(f.name);
std::vector<X> i;
while (!p.eof()) {
try {
auto a = p.template get_next<int, double, std::string>();
if (p.valid()) {
i.emplace_back(ss::to_object<X>(a));
} else {
++num_errors;
}
} catch (const std::exception& e) {
++num_errors;
}
}
CHECK(bad_lines == num_errors);
std::vector<X> data = {{1, 2, "just\n\nstrings"},
#ifndef _WIN32
{3, 4, "just\r\nsome\r\n\n\nstrings"},
#else
{3, 4, "just\nsome\n\n\nstrings"},
#endif
{9, 10, "just strings"}};
for (auto& [_, __, s] : i) {
update_if_crlf(s);
}
CHECK_EQ(i, data);
}

314
test/test_parser1_3.cpp Normal file
View File

@ -0,0 +1,314 @@
#include "test_parser1.hpp"
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;
#ifndef _WIN32
out << "3,4,\"ju\n\r\n\nnk\"" << std::endl;
out << "5,6,just\\\n\\\r\nstrings" << std::endl;
#else
out << "3,4,\"ju\n\n\nnk\"" << std::endl;
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 = 20;
auto num_errors = 0;
auto [p, _] =
make_parser<buffer_mode, ErrorMode, ss::multiline_restricted<2>,
ss::quote<'"'>, ss::escape<'\\'>>(f.name, ",");
std::vector<X> i;
while (!p.eof()) {
try {
auto a = p.template get_next<int, double, std::string>();
if (p.valid()) {
i.emplace_back(ss::to_object<X>(a));
} else {
++num_errors;
}
} catch (const std::exception& e) {
++num_errors;
}
}
CHECK(bad_lines == num_errors);
std::vector<X> data = {{1, 2, "just\n\nstrings"},
#ifndef _WIN32
{5, 6, "just\n\r\nstrings"},
#else
{5, 6, "just\n\nstrings"},
#endif
{9, 10, "just\n\nstrings"},
{19, 20, "just strings"}};
for (auto& [_, __, s] : i) {
update_if_crlf(s);
}
if (i.size() != data.size()) {
CHECK_EQ(i.size(), data.size());
}
CHECK_EQ(i, data);
}
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;
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, ErrorMode, Ts...>(f.name);
size_t line = 0;
while (!p.eof()) {
auto command = [&p = p] {
std::ignore = p.template get_next<int, double, std::string>();
};
if (line == bad_line) {
expect_error_on_command(p, command);
break;
} else {
command();
CHECK(p.valid());
++line;
}
}
}
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<'"'>;
// unterminated escape
{
const std::vector<std::string> lines{"1,2,just\\"};
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<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<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<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<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<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<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<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<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<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<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<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<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<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<T, multiline, escape, quote>(lines, 0);
}
{
const std::vector<std::string> lines{"1,2,\"just\n\\"};
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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<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<T, multiline, escape, quote>(lines, 1);
}
}

400
test/test_parser1_4.cpp Normal file
View File

@ -0,0 +1,400 @@
#include "test_parser1.hpp"
template <typename T, typename Tuple>
struct has_type;
template <typename T, typename... Us>
struct has_type<T, std::tuple<Us...>>
: std::disjunction<std::is_same<T, Us>...> {};
template <typename T, typename... Ts>
static void test_fields(const std::string file_name, const std::vector<X>& data,
const std::vector<std::string>& header,
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, ErrorMode>(file_name, ",");
CHECK_FALSE(p.field_exists("Unknown"));
p.use_fields(fields);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
std::vector<CaseType> i;
for (const auto& a : p.template iterate<CaseType>()) {
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
i.push_back(a);
}
CHECK_EQ(i.size(), data.size());
for (size_t j = 0; j < i.size(); ++j) {
if constexpr (has_type<int, CaseType>::value) {
CHECK_EQ(std::get<int>(i[j]), data[j].i);
}
if constexpr (has_type<double, CaseType>::value) {
CHECK_EQ(std::get<double>(i[j]), data[j].d);
}
if constexpr (has_type<std::string, CaseType>::value) {
CHECK_EQ(std::get<std::string>(i[j]), data[j].s);
}
}
}
TEST_CASE_TEMPLATE("test various cases with header", T,
ParserOptionCombinations) {
unique_file_name f{"various_cases_with_header"};
using str = std::string;
constexpr static auto Int = "Int";
constexpr static auto Dbl = "Double";
constexpr static auto Str = "String";
const std::vector<std::string> header{Int, Dbl, Str};
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, header);
const auto& o = f.name;
const auto& d = data;
{
ss::parser<ss::string_error> p{f.name, ","};
std::vector<X> i;
for (const auto& a : p.iterate<int, double, std::string>()) {
CHECK_EQ(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
i.emplace_back(ss::to_object<X>(a));
}
CHECK_NE(i, data);
}
{
ss::parser<ss::string_error> p{f.name, ","};
std::vector<X> i;
p.ignore_next();
for (const auto& a : p.iterate<int, double, std::string>()) {
CHECK_EQ(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
i.emplace_back(ss::to_object<X>(a));
}
CHECK_EQ(i, data);
}
{
ss::parser<ss::string_error> p{f.name, ","};
CHECK_EQ(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
p.use_fields(Int, Dbl);
CHECK_EQ(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
{
auto [int_, double_] = p.get_next<int, double>();
CHECK_EQ(int_, data[0].i);
CHECK_EQ(double_, data[0].d);
}
p.use_fields(Dbl, Int);
CHECK_EQ(header, p.header());
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
{
auto [double_, int_] = p.get_next<double, int>();
CHECK_EQ(int_, data[1].i);
CHECK_EQ(double_, data[1].d);
}
p.use_fields(Str);
{
auto string_ = p.get_next<std::string>();
CHECK_EQ(string_, data[2].s);
}
p.use_fields(Str, Int, Dbl);
{
auto [string_, int_, double_] =
p.get_next<std::string, int, double>();
CHECK_EQ(double_, data[3].d);
CHECK_EQ(int_, data[3].i);
CHECK_EQ(string_, data[3].s);
}
}
/* python used to generate permutations
import itertools
header = {'str': 'Str',
'double': 'Dbl',
'int': 'Int'}
keys = ['str', 'int', 'double']
for r in range (1, 3):
combinations = list(itertools.permutations(keys, r = r))
for combination in combinations:
template_params = []
arg_params = []
for type in combination:
template_params.append(type)
arg_params.append(header[type])
call = 'testFields<' + ', '.join(template_params) + \
'>(o, d, header, {' + ', '.join(arg_params) + '});'
print(call)
*/
test_fields<T, str>(o, d, header, {Str});
test_fields<T, int>(o, d, header, {Int});
test_fields<T, double>(o, d, header, {Dbl});
test_fields<T, str, int>(o, d, header, {Str, Int});
test_fields<T, str, double>(o, d, header, {Str, Dbl});
test_fields<T, int, str>(o, d, header, {Int, Str});
test_fields<T, int, double>(o, d, header, {Int, Dbl});
test_fields<T, double, str>(o, d, header, {Dbl, Str});
test_fields<T, double, int>(o, d, header, {Dbl, Int});
test_fields<T, str, int, double>(o, d, header, {Str, Int, Dbl});
test_fields<T, str, double, int>(o, d, header, {Str, Dbl, Int});
test_fields<T, int, str, double>(o, d, header, {Int, Str, Dbl});
test_fields<T, int, double, str>(o, d, header, {Int, Dbl, Str});
test_fields<T, double, str, int>(o, d, header, {Dbl, Str, Int});
test_fields<T, double, int, str>(o, d, header, {Dbl, Int, Str});
}
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;
auto check_header = [&lines](auto& p) {
if (lines.empty()) {
CHECK_EQ(p.header().size(), 1);
CHECK_EQ(p.header().at(0), "");
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
} else {
CHECK_EQ(lines[0], merge_header(p.header()));
CHECK_EQ(merge_header(p.header(), ","), p.raw_header());
}
CHECK(p.valid());
};
unique_file_name f{"invalid_fields"};
{
std::ofstream out{f.name};
for (const auto& line : lines) {
out << line << std::endl;
}
}
{
// No fields specified
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p] { p.use_fields(); };
expect_error_on_command(p, command);
check_header(p);
}
{
// Unknown field
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p] { p.use_fields("Unknown"); };
expect_error_on_command(p, command);
check_header(p);
}
{
// Field used multiple times
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p, &fields = fields] {
p.use_fields(fields.at(0), fields.at(0));
};
if (!fields.empty()) {
expect_error_on_command(p, command);
}
check_header(p);
}
{
// Mapping out of range
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p, &fields = fields] {
p.use_fields(fields.at(0));
std::ignore = p.template get_next<std::string, std::string>();
};
check_header(p);
if (!fields.empty()) {
expect_error_on_command(p, command);
}
check_header(p);
}
{
// Invalid header
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name, ",");
auto command = [&p = p, &fields = fields] { p.use_fields(fields); };
check_header(p);
if (!fields.empty()) {
// Pass if there are no duplicates, fail otherwise
if (std::unordered_set<std::string>{fields.begin(), fields.end()}
.size() != fields.size()) {
expect_error_on_command(p, command);
} else {
command();
CHECK(p.valid());
if (!p.valid()) {
if constexpr (T::StringError) {
std::cout << p.error_msg() << std::endl;
}
}
}
}
check_header(p);
}
}
TEST_CASE_TEMPLATE("test invalid header 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_TEMPLATE("test invalid rows with header", T,
ParserOptionCombinations) {
constexpr auto buffer_mode = T::BufferMode::value;
using ErrorMode = typename T::ErrorMode;
unique_file_name f{"invalid_rows_with_header"};
{
std::ofstream out{f.name};
out << "Int,String,Double" << std::endl;
out << "1,line1,2.34" << std::endl;
out << "2,line2" << std::endl;
out << "3,line3,67.8" << std::endl;
out << "4,line4,67.8,9" << std::endl;
out << "5,line5,9.10" << std::endl;
out << "six,line6,10.11" << std::endl;
}
std::vector<std::string> header = {"Int", "String", "Double"};
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
p.use_fields("Int", "String", "Double");
using data = std::tuple<int, std::string, double>;
std::vector<data> i;
CHECK(p.valid());
while (!p.eof()) {
try {
const auto& t = p.template get_next<data>();
if (p.valid()) {
i.push_back(t);
}
} catch (const ss::exception&) {
continue;
}
}
std::vector<data> expected = {{1, "line1", 2.34},
{3, "line3", 67.8},
{5, "line5", 9.10}};
CHECK_EQ(i, expected);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
p.use_fields("Double", "Int");
using data = std::tuple<double, int>;
std::vector<data> i;
CHECK(p.valid());
while (!p.eof()) {
try {
const auto& t = p.template get_next<data>();
if (p.valid()) {
i.push_back(t);
}
} catch (const ss::exception&) {
continue;
}
}
std::vector<data> expected = {{2.34, 1}, {67.8, 3}, {9.10, 5}};
CHECK_EQ(i, expected);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
}
{
auto [p, _] = make_parser<buffer_mode, ErrorMode>(f.name);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
p.use_fields("String", "Double");
using data = std::tuple<std::string, double>;
std::vector<data> i;
CHECK(p.valid());
while (!p.eof()) {
try {
const auto& t = p.template get_next<data>();
if (p.valid()) {
i.push_back(t);
}
} catch (const ss::exception&) {
continue;
}
}
std::vector<data> expected = {{"line1", 2.34},
{"line3", 67.8},
{"line5", 9.10},
{"line6", 10.11}};
CHECK_EQ(i, expected);
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(merge_header(p.header()), p.raw_header());
}
}

301
test/test_parser1_5.cpp Normal file
View File

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

View File

@ -1,13 +1,7 @@
#include "test_helpers.hpp"
#include <algorithm>
#include <filesystem>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <regex>
#include <ss/parser.hpp>
#include <sstream>
#include <unordered_map>
#include <unordered_set>
#ifndef SEGMENT_NAME
@ -91,7 +85,7 @@ struct column {
};
template <typename... Ts>
column make_column(const std::string& input_header,
[[nodiscard]] column make_column(const std::string& input_header,
const std::vector<field>& input_fields) {
using setup = ss::setup<Ts...>;
std::vector<field> filtered_fields;
@ -133,8 +127,8 @@ column make_column(const std::string& input_header,
}
template <typename... Ts>
std::vector<std::string> generate_csv_data(const std::vector<field>& data,
const std::string& delim) {
[[nodiscard]] std::vector<std::string> generate_csv_data(
const std::vector<field>& data, const std::string& delim) {
(void)delim;
using setup = ss::setup<Ts...>;
constexpr static auto escape = '\\';
@ -257,9 +251,12 @@ std::vector<std::string> generate_csv_data(const std::vector<field>& data,
}
[[maybe_unused]] void write_to_file(const std::vector<std::string>& data,
const std::string& delim, const std::string& file_name) {
const std::string& delim,
const std::string& file_name,
bool add_new_line = true) {
std::ofstream out{file_name, std::ios_base::app};
std::string line;
for (size_t i = 0; i < data.size(); ++i) {
line += data[i];
if (i != data.size() - 1) {
@ -267,7 +264,10 @@ std::vector<std::string> generate_csv_data(const std::vector<field>& data,
}
}
out << line << std::endl;
out << line;
if (add_new_line) {
out << std::endl;
}
}
#define CHECK_EQ_CRLF(V1, V2) \
@ -299,7 +299,7 @@ std::vector<std::string> generate_csv_data(const std::vector<field>& data,
CHECK(V1 == V2); \
}
template <typename... Ts>
template <bool buffer_mode, typename... Ts>
void test_data_combinations(const std::vector<column>& input_data,
const std::string& delim, bool include_header) {
using setup = ss::setup<Ts...>;
@ -308,7 +308,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;
@ -327,10 +327,16 @@ void test_data_combinations(const std::vector<column>& input_data,
field_header.push_back(field{el.header});
}
std::string header_line;
if (include_header) {
auto header_data = generate_csv_data<Ts...>(field_header, delim);
header_line = merge_header(header_data, delim);
if (input_data.size() == 0 && rand() % 10 == 0) {
write_to_file(header_data, delim, f.name, false);
} else {
write_to_file(header_data, delim, f.name);
}
}
std::vector<int> layout;
size_t n = 1 + rng.rand() % 5;
@ -354,15 +360,12 @@ void test_data_combinations(const std::vector<column>& input_data,
expected_data.push_back(raw_data);
auto data = generate_csv_data<Ts...>(raw_data, delim);
write_to_file(data, delim, f.name);
/*
std::cout << "[.";
for (const auto& el : data) {
std::cout << el << '.';
if (i + 1 == n && rand() % 10 == 0) {
write_to_file(data, delim, f.name, false);
} else {
write_to_file(data, delim, f.name);
}
std::cout << "]" << std::endl;
*/
}
auto layout_combinations = include_header && !setup::ignore_header
@ -388,7 +391,7 @@ void test_data_combinations(const std::vector<column>& input_data,
}
for (const auto& layout : unique_layout_combinations) {
ss::parser<setup> p{f.name, delim};
auto [p, _] = make_parser<buffer_mode, setup>(f.name, delim);
if (include_header && !setup::ignore_header) {
std::vector<std::string> fields;
@ -396,7 +399,9 @@ void test_data_combinations(const std::vector<column>& input_data,
fields.push_back(header[index]);
}
if constexpr (!setup::ignore_header) {
p.use_fields(fields);
}
if (!p.valid()) {
if constexpr (setup::string_error) {
@ -409,7 +414,7 @@ void test_data_combinations(const std::vector<column>& input_data,
REQUIRE(p.valid());
}
auto check_error = [&p] {
auto check_error = [&p = p] {
CHECK(p.valid());
if (!p.valid()) {
if constexpr (setup::string_error) {
@ -418,15 +423,25 @@ void test_data_combinations(const std::vector<column>& input_data,
}
};
auto check_header = [&p = p, &header = header, include_header,
header_line] {
if (include_header) {
if constexpr (!setup::ignore_header) {
CHECK_EQ_ARRAY(header, p.header());
CHECK_EQ(header_line, p.raw_header());
}
}
};
int num_columns = layout.size();
for (size_t i = 0; i < n + 1; ++i) {
check_header();
try {
switch (num_columns) {
case 1: {
auto s0 = p.template get_next<std::string>();
if (i < n) {
check_error();
// std::cout << s0 << std::endl;
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
} else {
CHECK(p.eof());
@ -439,7 +454,6 @@ void test_data_combinations(const std::vector<column>& input_data,
p.template get_next<std::string, std::string>();
if (i < n) {
check_error();
// std::cout << s0 << ' ' << s1 << std::endl;
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
} else {
@ -454,8 +468,6 @@ void test_data_combinations(const std::vector<column>& input_data,
std::string>();
if (i < n) {
check_error();
// std::cout << s0 << ' ' << s1 << ' ' << s2 <<
// std::endl;
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
CHECK_EQ_CRLF(s2, expected_data[i][layout[2]].value);
@ -471,10 +483,6 @@ void test_data_combinations(const std::vector<column>& input_data,
std::string, std::string>();
if (i < n) {
check_error();
/*
std::cout << s0 << ' ' << s1 << ' ' << s2 << ' ' << s3
<< std::endl;
*/
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
CHECK_EQ_CRLF(s2, expected_data[i][layout[2]].value);
@ -492,9 +500,6 @@ void test_data_combinations(const std::vector<column>& input_data,
std::string>();
if (i < n) {
check_error();
// std::cout << s0 << ' ' << s1 << ' ' << s2 << ' ' <<
// s3
// << ' ' << s4 << std::endl;
CHECK_EQ_CRLF(s0, expected_data[i][layout[0]].value);
CHECK_EQ_CRLF(s1, expected_data[i][layout[1]].value);
CHECK_EQ_CRLF(s2, expected_data[i][layout[2]].value);
@ -570,8 +575,14 @@ void test_option_combinations() {
{columns0, columns1, columns2, columns3, columns4, columns5,
columns6, columns7}) {
try {
test_data_combinations<Ts...>(columns, delimiter, false);
test_data_combinations<Ts...>(columns, delimiter, true);
test_data_combinations<false, Ts...>(columns, delimiter,
false);
test_data_combinations<false, Ts...>(columns, delimiter,
true);
test_data_combinations<true, Ts...>(columns, delimiter,
false);
test_data_combinations<true, Ts...>(columns, delimiter,
true);
} catch (std::exception& e) {
std::cout << typeid(ss::parser<Ts...>).name() << std::endl;
FAIL_CHECK(std::string{e.what()});
@ -614,8 +625,9 @@ void test_option_combinations3() {
test_option_combinations2<Ts..., trim>();
}
} /* namespace */
} /* anonymous namespace */
// Tests split into multiple compilation units
#if 0
TEST_CASE("parser test various cases version 2 segment 1") {
@ -627,25 +639,33 @@ TEST_CASE("parser test various cases version 2 segment 1") {
using multiline_r = ss::multiline_restricted<10>;
using trimr = ss::trim_right<' '>;
using triml = ss::trim_left<' '>;
using trim = ss::trim<' '>;
// segment 1
test_option_combinations3<>();
test_option_combinations3<escape>();
test_option_combinations3<quote>();
// segment 2
test_option_combinations3<quote>();
test_option_combinations3<escape, quote>();
// segment 3
test_option_combinations3<escape, multiline>();
test_option_combinations3<quote, multiline>();
// segment 3
// segment 4
test_option_combinations3<escape, quote, multiline>();
test_option_combinations3<escape, quote, multiline_r>();
// segment 4
// segment 5
test_option_combinations<escape, quote, multiline, triml>();
test_option_combinations<escape, quote, multiline, trimr>();
// segment 6
test_option_combinations3<escape, quote, multiline>();
test_option_combinations3<escape, quote, multiline, trim>();
#else
test_option_combinations3<escape, quote, multiline>();
#endif
}

View File

@ -3,12 +3,9 @@
TEST_CASE("parser test various cases version 2 segment 1") {
#ifdef CMAKE_GITHUB_CI
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
test_option_combinations3<>();
test_option_combinations3<escape>();
test_option_combinations3<quote>();
#endif
}

View File

@ -5,11 +5,8 @@ TEST_CASE("parser test various cases version 2 segment 2") {
#ifdef CMAKE_GITHUB_CI
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
using multiline = ss::multiline;
test_option_combinations3<quote>();
test_option_combinations3<escape, quote>();
test_option_combinations3<escape, multiline>();
test_option_combinations3<quote, multiline>();
#endif
}

View File

@ -6,10 +6,8 @@ TEST_CASE("parser test various cases version 2 segment 3") {
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
using multiline = ss::multiline;
using multiline_r = ss::multiline_restricted<10>;
test_option_combinations3<escape, quote, multiline>();
test_option_combinations3<escape, quote, multiline_r>();
test_option_combinations3<escape, multiline>();
test_option_combinations3<quote, multiline>();
#endif
}

View File

@ -2,18 +2,13 @@
#include "test_parser2.hpp"
TEST_CASE("parser test various cases version 2 segment 4") {
#ifdef CMAKE_GITHUB_CI
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
using multiline = ss::multiline;
using multiline_r = ss::multiline_restricted<10>;
#ifdef CMAKE_GITHUB_CI
using trimr = ss::trim_right<' '>;
using triml = ss::trim_left<' '>;
test_option_combinations<escape, quote, multiline, triml>();
test_option_combinations<escape, quote, multiline, trimr>();
#else
test_option_combinations3<escape, quote, multiline>();
test_option_combinations3<escape, quote, multiline_r>();
#endif
}

15
test/test_parser2_5.cpp Normal file
View File

@ -0,0 +1,15 @@
#define SEGMENT_NAME "segment5"
#include "test_parser2.hpp"
TEST_CASE("parser test various cases version 2 segment 5") {
#ifdef CMAKE_GITHUB_CI
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
using multiline = ss::multiline;
using trimr = ss::trim_right<' '>;
using triml = ss::trim_left<' '>;
test_option_combinations<escape, quote, multiline, triml>();
test_option_combinations<escape, quote, multiline, trimr>();
#endif
}

10
test/test_parser2_6.cpp Normal file
View File

@ -0,0 +1,10 @@
#define SEGMENT_NAME "segment6"
#include "test_parser2.hpp"
TEST_CASE("parser test various cases version 2 segment 6") {
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
using multiline = ss::multiline;
test_option_combinations3<escape, quote, multiline>();
}

View File

@ -3,12 +3,14 @@
set -x
set -e
python3 script/single_header_generator.py > ssp.cpp
TMP_HDR=test_single_header.hpp
TMP_SRC=test_single_header.cpp
TMP_BIN=test_single_header
echo 'int main(){ ss::parser p{""}; p.get_next<int, float>(); return 0; }' \
>> ssp.cpp
python3 script/single_header_generator.py > ${TMP_HDR}
cat ${TMP_HDR} test/test_single_header_main.txt > ${TMP_SRC}
g++ -std=c++17 ssp.cpp -o ssp.bin -Wall -Wextra
./ssp.bin
g++ -std=c++17 ${TMP_SRC} -o ${TMP_BIN} -Wall -Wextra
./${TMP_BIN}
rm ssp.cpp ssp.bin
rm ${TMP_HDR} ${TMP_SRC} ${TMP_BIN}

View File

@ -0,0 +1,12 @@
int main() {
using quote = ss::quote<'"'>;
using escape = ss::escape<'\\'>;
using trim = ss::trim<' '>;
std::string data = "1,string,2.34,c";
ss::parser<quote, escape, trim, ss::multiline> p{data.c_str(), data.size()};
auto tup = p.get_next<int, std::string, float, std::optional<char>>();
return 0;
}

View File

@ -145,7 +145,7 @@ make_combinations(const std::vector<std::string>& input,
return {std::move(lines), std::move(expectations)};
}
} /* namespace */
} /* anonymous namespace */
/* ********************************** */
/* ********************************** */
@ -548,7 +548,7 @@ public:
return splitter.size_shifted();
}
};
} /* ss */
} /* namespace ss */
TEST_CASE("splitter test resplit unterminated quote") {