61 Commits

Author SHA1 Message Date
red0124
57abdb3923 Merge pull request #26 from red0124/bugfix/terminated_escape_on_quoted_multiline
Fix bug where the parsing of data containing escaped new lines within…
2023-07-31 23:05:52 +02:00
ado
27ef7f2e21 Fix bug where the parsing of data containing escaped new lines within quotes would produce bad outputs 2023-07-31 21:49:15 +02:00
ado
f58091cf02 Fix test_parser.cpp formating 2023-07-29 16:51:34 +02:00
red0124
78c75abec7 Merge pull request #25 from red0124/feature/win_msvc_ci
Feature/win msvc ci
2023-07-29 16:25:26 +02:00
ado
76091263f2 Fix win-msvc ci badge 2023-07-29 16:11:27 +02:00
ado
d99cba274e Add win-msvc ci badge 2023-07-29 16:08:33 +02:00
ado
e70fc05646 Fix msvc ci configure step 2023-07-29 15:39:51 +02:00
ado
21d1096edf Fix msvc ci yaml issues 2023-07-29 15:37:53 +02:00
ado
72b74a3b64 Fix msvc ci configure step 2023-07-29 15:35:29 +02:00
ado
b7f6009eb9 Fix msvc ci yaml issues 2023-07-29 15:30:05 +02:00
ado
bdfd896861 Fix msvc vs 16 ci 2023-07-29 15:17:39 +02:00
ado
d6bc8086b2 Fix msvc ci configure step 2023-07-29 15:03:25 +02:00
ado
f762736da2 Fix msvc ci configure step 2023-07-29 14:59:49 +02:00
ado
739077a8db Fix msvc ci vs version names 2023-07-29 14:54:25 +02:00
ado
3dcb2ce2ef Add vs16 for msvc ci 2023-07-29 14:22:41 +02:00
ado
e4d9e10ac3 Update msvc ci 2023-07-29 14:14:02 +02:00
ado
a98742945b Update test helpers 2023-07-29 13:56:00 +02:00
ado
5d6c2c4af5 Remove move count unit test from parser tests 2023-07-29 01:44:19 +02:00
ado
58857fff2d Add config option for ctest within win-msvc.yml 2023-07-29 01:18:11 +02:00
ado
e6a7e09975 Add config option for ctest within win-msvc.yml 2023-07-29 01:18:01 +02:00
ado
705ce422f0 Add constexpr values to lambda to handle msvc issue with respect to captured constexpr values 2023-07-29 01:11:29 +02:00
ado
75f5ee2a55 Add _CRT_SECURE_NO_WARNINGS define to remove msvc warnings 2023-07-29 00:56:08 +02:00
ado
6baeb2d598 Add _CRT_SECURE_NO_WARNINGS define to remove msvc warnings 2023-07-29 00:53:04 +02:00
ado
7b1f49d304 Add _CRT_SECURE_NO_WARNINGS define to remove msvc warnings 2023-07-29 00:49:06 +02:00
ado
515ddad997 Add _CRT_SECURE_NO_WARNINGS define to remove msvc warnings 2023-07-29 00:45:33 +02:00
ado
fd39b5eef2 Try msvc ci 2023-07-29 00:38:27 +02:00
red0124
77185cb366 Merge pull request #24 from red0124/improvement/markdown_cicd_badge_link
Add hyperlink for cicd badges
2023-07-28 21:30:10 +02:00
ado
ad991d6a7d Add hyperlink for cicd badges 2023-07-28 21:28:32 +02:00
red0124
304ca6ef0f Merge pull request #23 from red0124/bugfix/single_header_include_update
Update ssp.hpp to handle issue fixed in the previous commit
2023-07-25 11:24:54 +02:00
ado
103ff33f21 Update ssp.hpp to handle issue fixed in the previous commit 2023-07-25 11:13:32 +02:00
red0124
8b928de086 Merge pull request #22 from red0124/bugfix/multiline_on_custom_delimiter
Fix bug where the default delimiter would be used for multiline data
2023-07-25 00:46:43 +02:00
ado
6edce88d79 Fix bug where the default delimiter would be used for multiline data 2023-07-25 00:36:53 +02:00
red0124
6196f7796b Merge pull request #21 from red0124/improvement/markdown_labels
Update README links to sections
2023-07-20 00:21:38 +02:00
ado
5672aa635e Update README links to sections 2023-07-20 00:19:31 +02:00
ado
3eefac93b1 Fix README issues 2023-05-25 01:10:37 +02:00
ado
774f452689 fix typo 2023-03-19 19:48:22 +01:00
ado
448066b173 Update version 2023-02-18 14:20:32 +01:00
red0124
a7eca6064a Merge pull request #20 from red0124/improvement/conditional_t
Replace ss::ternary_t with std::conditional_t
2023-02-12 12:57:36 +01:00
ado
a4ecbd4dc8 Replace ss::ternary_t with std::conditional_t 2023-02-12 12:45:49 +01:00
red0124
a9e9783e6a Merge pull request #19 from red0124/improvement/doctest_update
Make project used forked doctest
2023-02-12 12:38:34 +01:00
ado
04edf1e532 Make project used forked doctest 2023-02-12 12:23:25 +01:00
red0124
8dcb69aa2c Merge pull request #18 from red0124/improvement/cmake_update
Update CMake files
2023-02-09 20:20:27 +01:00
ado
db2a72c18b Update CMake files 2023-02-08 21:54:44 +01:00
red0124
0e28a06799 Merge pull request #17 from red0124/improvement/meson_update
Improvement/meson update
2023-02-07 23:29:29 +01:00
ado
2218b01b81 Merge remote-tracking branch 'origin/master' into improvement/meson_update 2023-02-07 23:22:36 +01:00
ado
f777b04eb8 Fix failing mingw unit test 2023-02-01 22:45:50 +01:00
ado
7831bbd735 Add forcefallback wrap_mode option in meson.build 2023-02-01 01:22:43 +01:00
ado
6efb39b2db Add version number to meson.build 2023-02-01 00:57:32 +01:00
ado
f2b49e6d6c Update meson dependency usage 2023-02-01 00:52:23 +01:00
ado
5cb3c65b24 Update doctest revision 2023-01-31 21:23:04 +01:00
red0124
d58644fd67 Merge pull request #16 from red0124/feature/single_header
Feature/single header
2022-03-30 20:26:51 +02:00
ado
56447eb1d6 update README 2022-03-30 20:21:23 +02:00
ado
86d732e743 update README 2022-03-30 20:20:13 +02:00
ado
031ab5f7fc update README 2022-03-30 20:18:18 +02:00
ado
420625b25c update README 2022-03-30 20:15:14 +02:00
ado
9fa9edb24d add spp.hpp 2022-03-30 20:11:55 +02:00
ado
a2d72cdef3 remove unused script 2022-03-30 19:56:57 +02:00
ado
c214975ca0 add unit tests for conversion without the fast float library 2022-03-30 19:54:50 +02:00
ado
d328f7d59d add test_single_header.sh 2022-03-30 18:35:10 +02:00
ado
62055f03c7 add script to generate single header 2022-03-30 18:14:30 +02:00
ado
999992e579 fix README typos 2022-03-29 12:31:59 +02:00
22 changed files with 3384 additions and 237 deletions

63
.github/workflows/win-msvc.yml vendored Normal file
View File

@@ -0,0 +1,63 @@
name: win-msvc-ci
on:
push:
branches:
- master
- feature/**
- improvement/**
- bugfix/**
pull_request:
branches:
- master
- feature/**
- improvement/**
- bugfix/**
jobs:
ci:
if: >-
! contains(toJSON(github.event.commits.*.message), '[skip ci]') &&
! contains(toJSON(github.event.commits.*.message), '[skip github]')
defaults:
run:
shell: bash
runs-on: ${{ matrix.config.os }}
strategy:
fail-fast: false
matrix:
config:
- os: windows-2019
vs: "Visual Studio 16 2019"
- os: windows-latest
vs: "Visual Studio 17 2022"
build: [Debug, Release]
platform: [Win32, x64]
name: "${{matrix.config.vs}}:${{matrix.platform}}:${{matrix.build}}"
steps:
- name: checkout
uses: actions/checkout@v2
- name: Install dependencies
run: script/ci_install_deps.sh
- name: Configure
run: >-
cmake -S test -B build -D CMAKE_BUILD_TYPE=${{matrix.build}}
-G "${{matrix.config.vs}}" -A ${{matrix.platform}}
- name: Build
run: cmake --build build -j ${{steps.cores.outputs.count}}
- name: Run
working-directory: build
run: >-
ctest -C Debug --output-on-failure -j ${{steps.cores.outputs.count}}

2
.gitignore vendored
View File

@@ -6,3 +6,5 @@ build/
hbuild/
subprojects/*
!subprojects/*.wrap
ssp.cpp
ssp.bin

View File

@@ -2,8 +2,8 @@ cmake_minimum_required(VERSION 3.14)
project(
ssp
VERSION 0.0.1
DESCRIPTION "Static split parser"
VERSION 1.4.0
DESCRIPTION "csv parser"
HOMEPAGE_URL "https://github.com/red0124/ssp"
LANGUAGES CXX
)
@@ -11,22 +11,23 @@ project(
# ---- Warning guard ----
# Protect dependents from this project's warnings if the guard isn't disabled
set(ssp_warning_guard SYSTEM)
if(ssp_INCLUDE_WITHOUT_SYSTEM)
set(ssp_warning_guard "")
set(SSP_WARNING_GUARD SYSTEM)
if(SSP_INCLUDE_WITHOUT_SYSTEM)
set(SSP_WARNING_GUARD "")
endif()
# ---- Dependencies ----
include(FetchContent)
FetchContent_Declare(
fast_float
GIT_REPOSITORY https://github.com/red0124/fast_float.git
GIT_TAG origin/meson
GIT_SHALLOW TRUE)
fetchcontent_declare(
fast_float
GIT_REPOSITORY https://github.com/red0124/fast_float.git
GIT_TAG origin/meson
GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(fast_float)
set(fast_float_source_dir "${FETCHCONTENT_BASE_DIR}/fast_float-src")
fetchcontent_makeavailable(fast_float)
set(FAST_FLOAT_SOURCE_DIR "${FETCHCONTENT_BASE_DIR}/fast_float-src")
# ---- Declare library ----
@@ -35,10 +36,10 @@ add_library(ssp::ssp ALIAS ssp)
target_include_directories(
ssp
${ssp_warning_guard}
${SSP_WARNING_GUARD}
INTERFACE
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/fast_float>"
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
"$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/fast_float>"
)
target_compile_features(ssp INTERFACE cxx_std_17)
@@ -46,8 +47,8 @@ target_compile_features(ssp INTERFACE cxx_std_17)
target_link_libraries(
ssp
INTERFACE
"$<$<AND:$<CXX_COMPILER_ID:AppleClang,Clang>,$<VERSION_LESS:$<CXX_COMPILER_VERSION>,9.0>>:c++fs>"
"$<$<AND:$<CXX_COMPILER_ID:GNU>,$<VERSION_LESS:$<CXX_COMPILER_VERSION>,9.1>>:stdc++fs>"
"$<$<AND:$<CXX_COMPILER_ID:AppleClang,Clang>,$<VERSION_LESS:$<CXX_COMPILER_VERSION>,9.0>>:c++fs>"
"$<$<AND:$<CXX_COMPILER_ID:GNU>,$<VERSION_LESS:$<CXX_COMPILER_VERSION>,9.1>>:stdc++fs>"
)
# ---- Install ----
@@ -55,19 +56,21 @@ target_link_libraries(
include(CMakePackageConfigHelpers)
include(GNUInstallDirs)
set(ssp_directory "ssp-${PROJECT_VERSION}")
set(ssp_include_directory "${CMAKE_INSTALL_INCLUDEDIR}")
set(SSP_DIRECTORY "ssp-${PROJECT_VERSION}")
set(SSP_INCLUDE_DIRECTORY "${CMAKE_INSTALL_INCLUDEDIR}")
install(
DIRECTORY "${PROJECT_SOURCE_DIR}/include/" "${fast_float_source_dir}/include/"
DESTINATION "${ssp_include_directory}"
DIRECTORY
"${PROJECT_SOURCE_DIR}/include/"
"${FAST_FLOAT_SOURCE_DIR}/include/"
DESTINATION "${SSP_INCLUDE_DIRECTORY}"
COMPONENT ssp_Development
)
install(
TARGETS ssp
EXPORT sspTargets
INCLUDES DESTINATION "${ssp_include_directory}"
INCLUDES DESTINATION "${SSP_INCLUDE_DIRECTORY}"
)
write_basic_package_version_file(
@@ -76,11 +79,11 @@ write_basic_package_version_file(
ARCH_INDEPENDENT
)
set(ssp_install_cmakedir "${CMAKE_INSTALL_LIBDIR}/cmake/${ssp_directory}")
set(SSP_INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/${SSP_DIRECTORY}")
install(
FILES "${PROJECT_BINARY_DIR}/ssp-config-version.cmake"
DESTINATION "${ssp_install_cmakedir}"
DESTINATION "${SSP_INSTALL_CMAKEDIR}"
COMPONENT ssp_Development
)
@@ -88,10 +91,10 @@ install(
EXPORT sspTargets
FILE ssp-config.cmake
NAMESPACE ssp::
DESTINATION "${ssp_install_cmakedir}"
DESTINATION "${SSP_INSTALL_CMAKEDIR}"
COMPONENT ssp_Development
)
if(CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
include(CPack)
include(CPack)
endif()

View File

@@ -8,11 +8,12 @@
```
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
![ubuntu-latest-gcc](https://github.com/red0124/ssp/workflows/ubuntu-latest-gcc-ci/badge.svg)
![ubuntu-latest-clang](https://github.com/red0124/ssp/workflows/ubuntu-latest-clang-ci/badge.svg)
![ubuntu-latest-icc](https://github.com/red0124/ssp/workflows/ubuntu-latest-icc-ci/badge.svg)
![windows-msys2-gcc](https://github.com/red0124/ssp/workflows/win-msys2-gcc-ci/badge.svg)
![windows-msys2-clang](https://github.com/red0124/ssp/workflows/win-msys2-clang-ci/badge.svg)
[![ubuntu-latest-gcc](https://github.com/red0124/ssp/workflows/ubuntu-latest-gcc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-gcc.yml)
[![ubuntu-latest-clang](https://github.com/red0124/ssp/workflows/ubuntu-latest-clang-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-clang.yml)
[![ubuntu-latest-icc](https://github.com/red0124/ssp/workflows/ubuntu-latest-icc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-icc.yml)
[![windows-msys2-gcc](https://github.com/red0124/ssp/workflows/win-msys2-gcc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/win-msys2-gcc.yml)
[![windows-msys2-clang](https://github.com/red0124/ssp/workflows/win-msys2-clang-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/win-msys2-clang.yml)
[![win-msvc-ci](https://github.com/red0124/ssp/workflows/win-msvc-ci/badge.svg)](https://github.com/red0124/ssp/actions/workflows/win-msvc.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)
@@ -53,22 +54,26 @@ Brian S. Wolfe 40 1.9
Bill (Heath) Gates 65 3.3
```
# Features
* [Works on any type](#Custom-conversions)
* [Works on any type](#custom-conversions)
* Easy to use
* No exceptions
* [Works with headers](#Headers)
* [Works with quotes, escapes and spacings](#Setup)
* [Works with values containing new lines](#Multiline)
* [Columns and rows can be ignored](#Special-types)
* [Works with headers](#headers)
* [Works with quotes, escapes and spacings](#setup)
* [Works with values containing new lines](#multiline)
* [Columns and rows can be ignored](#special-types)
* Works with any type of delimiter
* Can return whole objects composed of converted values
* [Descriptive error handling can be enabled](#Error-handling)
* [Restrictions can be added for each column](#Restrictions)
* [Works with `std::optional` and `std::variant`](#Special-types)
* [Descriptive error handling can be enabled](#error-handling)
* [Restrictions can be added for each column](#restrictions)
* [Works with `std::optional` and `std::variant`](#special-types)
* Works with **`CRLF`** and **`LF`**
* [Conversions can be chained if invalid](#Substitute-conversions)
* [Conversions can be chained if invalid](#substitute-conversions)
* Fast
# 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.
# Installation
```shell
@@ -78,8 +83,8 @@ $ cmake --configure .
$ sudo make install
```
*Note, this will also install the fast_float library*
The library supports [CMake](#Cmake) and [meson](#Meson) build systems
*Note, this will also install the fast_float library*
The library supports [CMake](#Cmake) and [meson](#Meson) build systems
# Usage
@@ -113,7 +118,7 @@ The header can be ignored using the **`ss::ignore_header`** [setup](#Setup) opti
```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 paser can also check if a field is present within the header by using the **`has_field`** method.
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.
```cpp
// ...
ss::parser p{"students.csv", ","};
@@ -374,7 +379,7 @@ if(std::holds_alternative<float>(grade)) {
```
## Restrictions
Custom **`restrictions`** can be used to narrow down the conversions of unwanted values. **`ss::ir`** (in range) and **`ss::ne`** (none empty) are one of those:
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:
```cpp
// ss::ne makes sure that the name is not empty
// ss::ir makes sure that the grade will be in range [0, 10]
@@ -468,7 +473,6 @@ The delimiter is " ", and the number of columns varies depending on which shape
```cpp
ss::parser p{"shapes.txt", " "};
if (!p.valid()) {
std::cout << p.error_msg() << std::endl;
exit(EXIT_FAILURE);
}
@@ -510,7 +514,7 @@ while (!p.eof()) {
```
It is quite hard to make an error this way since most things will be checked at compile time.
The **`try_next`** method works in a similar way as **`get_next`** but returns a **`composit`** which holds a **`tuple`** with an **`optional`** to the **`tuple`** returned by **`get_next`**. This **`composite`** has an **`or_else`** method (looks a bit like **`tl::expected`**) which is able to try additional conversions if the previous failed. **`or_else`** also returns a **`composite`**, but in its tuple is the **`optional`** to the **`tuple`** of the previous conversions and an **`optional`** to the **`tuple`** of the new conversion. (sounds more complicated than it is.
The **`try_next`** method works in a similar way as **`get_next`** but returns a **`composite`** which holds a **`tuple`** with an **`optional`** to the **`tuple`** returned by **`get_next`**. This **`composite`** has an **`or_else`** method (looks a bit like **`tl::expected`**) which is able to try additional conversions if the previous failed. **`or_else`** also returns a **`composite`**, but in its tuple is the **`optional`** to the **`tuple`** of the previous conversions and an **`optional`** to the **`tuple`** of the new conversion. (sounds more complicated than it is.
To fetch the **`tuple`** from the **`composite`** the **`values`** method is used. The value of the above used conversion would look something like this:
```cpp
@@ -628,6 +632,5 @@ revision = origin/master
```
Then simply fetch the dependency and it is ready to be used:
```meson
ssp_sub = subproject('ssp')
ssp_dep = ssp_sub.get_variable('ssp_dep')
ssp_dep = dependency('ssp')
```

View File

@@ -102,7 +102,7 @@ class converter {
constexpr static auto string_error = setup<Matchers...>::string_error;
constexpr static auto default_delimiter = ",";
using error_type = ss::ternary_t<string_error, std::string, bool>;
using error_type = std::conditional_t<string_error, std::string, bool>;
public:
// parses line with given delimiter, returns a 'T' object created with
@@ -194,7 +194,7 @@ private:
////////////////
const split_data& resplit(line_ptr_type new_line, ssize_t new_size,
const std::string& delim = default_delimiter) {
const std::string& delim) {
return splitter_.resplit(new_line, new_size, delim);
}

View File

@@ -2,21 +2,27 @@
#include "type_traits.hpp"
#include <cstring>
#include <fast_float/fast_float.h>
#include <functional>
#include <limits>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <variant>
#ifndef SSP_DISABLE_FAST_FLOAT
#include <fast_float/fast_float.h>
#else
#include <charconv>
#endif
namespace ss {
////////////////
// number converters
////////////////
#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) {
@@ -29,6 +35,22 @@ std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
return ret;
}
#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) {
T ret;
auto [ptr, ec] = std::from_chars(begin, end, ret);
if (ec != std::errc() || ptr != end) {
return std::nullopt;
}
return ret;
}
#endif
inline std::optional<short> from_char(char c) {
if (c >= '0' && c <= '9') {
return c - '0';

View File

@@ -17,7 +17,7 @@ class parser {
constexpr static auto string_error = setup<Matchers...>::string_error;
using multiline = typename setup<Matchers...>::multiline;
using error_type = ss::ternary_t<string_error, std::string, bool>;
using error_type = std::conditional_t<string_error, std::string, bool>;
constexpr static bool escaped_multiline_enabled =
multiline::enabled && setup<Matchers...>::escape::enabled;
@@ -151,8 +151,8 @@ public:
template <bool get_object, typename T, typename... Ts>
struct iterable {
struct iterator {
using value =
ss::ternary_t<get_object, T, no_void_validator_tup_t<T, Ts...>>;
using value = std::conditional_t<get_object, T,
no_void_validator_tup_t<T, Ts...>>;
iterator() : parser_{nullptr} {
}
@@ -600,6 +600,8 @@ private:
if constexpr (quoted_multiline_enabled) {
while (unterminated_quote()) {
size -= next_line_converter_.size_shifted();
if (multiline_limit_reached(limit)) {
return true;
}
@@ -623,7 +625,8 @@ private:
}
}
next_line_converter_.resplit(next_line_buffer_, size);
next_line_converter_.resplit(next_line_buffer_, size,
delim_);
}
}
@@ -664,9 +667,6 @@ private:
}
void undo_remove_eol(char* buffer, size_t& string_end) {
if (next_line_converter_.unterminated_quote()) {
string_end -= next_line_converter_.size_shifted();
}
if (crlf_) {
std::copy_n("\r\n\0", 3, buffer + string_end);
string_end += 2;

View File

@@ -109,10 +109,10 @@ struct get_matcher<Matcher, T, Ts...> {
struct is_matcher : is_instance_of_matcher<U, Matcher> {};
static_assert(count_v<is_matcher, T, Ts...> <= 1,
"the same matcher is cannot"
"the same matcher cannot"
"be defined multiple times");
using type = ternary_t<is_matcher<T>::value, T,
typename get_matcher<Matcher, Ts...>::type>;
using type = std::conditional_t<is_matcher<T>::value, T,
typename get_matcher<Matcher, Ts...>::type>;
};
template <template <char...> class Matcher>
@@ -149,8 +149,8 @@ struct get_multiline;
template <typename T, typename... Ts>
struct get_multiline<T, Ts...> {
using type = ternary_t<is_instance_of_multiline<T>::value, T,
typename get_multiline<Ts...>::type>;
using type = std::conditional_t<is_instance_of_multiline<T>::value, T,
typename get_multiline<Ts...>::type>;
};
template <>
@@ -227,8 +227,10 @@ public:
using quote = get_matcher_t<quote, Ts...>;
using escape = get_matcher_t<escape, Ts...>;
using trim_left = ternary_t<trim_all::enabled, trim_all, trim_left_only>;
using trim_right = ternary_t<trim_all::enabled, trim_all, trim_right_only>;
using trim_left =
std::conditional_t<trim_all::enabled, trim_all, trim_left_only>;
using trim_right =
std::conditional_t<trim_all::enabled, trim_all, trim_right_only>;
using multiline = get_multiline_t<Ts...>;
constexpr static bool string_error = (count_string_error == 1);

View File

@@ -23,10 +23,10 @@ private:
constexpr static auto string_error = setup<Ts...>::string_error;
constexpr static auto is_const_line = !quote::enabled && !escape::enabled;
using error_type = ss::ternary_t<string_error, std::string, bool>;
using error_type = std::conditional_t<string_error, std::string, bool>;
public:
using line_ptr_type = ternary_t<is_const_line, const char*, char*>;
using line_ptr_type = std::conditional_t<is_const_line, const char*, char*>;
bool valid() const {
if constexpr (string_error) {

View File

@@ -357,26 +357,6 @@ struct is_instance_of<Template, Template<Ts...>> {
template <template <typename...> class Template, typename... Ts>
constexpr bool is_instance_of_v = is_instance_of<Template, Ts...>::value;
////////////////
// ternary
////////////////
template <bool B, typename T, typename U>
struct ternary;
template <typename T, typename U>
struct ternary<true, T, U> {
using type = T;
};
template <typename T, typename U>
struct ternary<false, T, U> {
using type = U;
};
template <bool B, typename T, typename U>
using ternary_t = typename ternary<B, T, U>::type;
////////////////
// tuple to struct
////////////////

View File

@@ -1,17 +1,24 @@
project('ssp', 'cpp',
project(
'ssp',
['cpp'],
default_options :
['warning_level=3',
'cpp_std=c++17',
'buildtype=debugoptimized'])
['warning_level=3',
'cpp_std=c++17',
'buildtype=debugoptimized',
'wrap_mode=forcefallback'],
version: '1.4.0',
meson_version:'>=0.54.0')
fast_float_sub = subproject('fast_float')
fast_float_dep = fast_float_sub.get_variable('fast_float_dep')
fast_float_dep = dependency('fast_float')
ssp_dep = declare_dependency(
include_directories: include_directories('include'),
dependencies: fast_float_dep
)
meson.override_dependency('ssp', ssp_dep)
if not meson.is_subproject()
subdir('test')
endif

View File

@@ -5,7 +5,7 @@ BUILD_TYPE=Debug
set -eux
git clone https://github.com/onqtam/doctest -b 2.4.4 --depth 1
git clone https://github.com/red0124/doctest -b master --depth 1
cmake -S doctest -B doctest/build \
-D CMAKE_BUILD_TYPE=${BUILD_TYPE} \

View File

@@ -0,0 +1,34 @@
#!/bin/python3
headers_dir = 'include/ss/'
headers = ['type_traits.hpp',
'function_traits.hpp',
'restrictions.hpp',
'common.hpp',
'setup.hpp',
'splitter.hpp',
'extract.hpp',
'converter.hpp',
'parser.hpp']
combined_file = []
includes = []
for header in headers:
with open(headers_dir + header) as f:
for line in f.read().splitlines():
if '#include "' in line or '#include <fast_float' in line:
continue
if '#include <' in line:
includes.append(line)
continue
if '#pragma once' not in line:
combined_file.append(line)
includes = sorted(set(includes))
print('\n'.join(includes))
print('#define SSP_DISABLE_FAST_FLOAT')
print('\n'.join(combined_file))

2949
ssp.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,3 +1,3 @@
[wrap-git]
url = https://github.com/onqtam/doctest
revision = 2.4.4
url = https://github.com/red0124/doctest
revision = master

View File

@@ -4,34 +4,38 @@ project(ssp_tests CXX)
# ---- Dependencies ----
set(
ssp_INCLUDE_WITHOUT_SYSTEM
YES
CACHE
INTERNAL
"Turn the warning guard off to have errors appear in test builds"
)
include(FetchContent)
FetchContent_Declare(ssp SOURCE_DIR "${PROJECT_SOURCE_DIR}/..")
FetchContent_MakeAvailable(ssp)
fetchcontent_declare(ssp SOURCE_DIR "${PROJECT_SOURCE_DIR}/..")
fetchcontent_makeavailable(ssp)
if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang")
target_compile_options(ssp INTERFACE -Wall -Wextra)
target_compile_options(ssp INTERFACE -Wall -Wextra)
endif()
find_package(doctest 2.4.4 CONFIG REQUIRED)
# for doctest_discover_tests
include(doctest)
include(FetchContent)
fetchcontent_declare(
DOCTEST
GIT_REPOSITORY https://github.com/red0124/doctest
GIT_TAG origin/master
GIT_SHALLOW TRUE
)
fetchcontent_makeavailable(DOCTEST)
set(DOCTEST "${FETCHCONTENT_BASE_DIR}/doctest-src")
# ---- Test ----
enable_testing()
foreach(name IN ITEMS test_splitter test_parser test_converter test_extractions)
add_executable("${name}" "${name}.cpp")
target_link_libraries("${name}" PRIVATE ssp::ssp fast_float doctest::doctest)
target_compile_definitions("${name}" PRIVATE
DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN CMAKE_GITHUB_CI)
doctest_discover_tests("${name}")
add_executable("${name}" "${name}.cpp")
target_link_libraries(
"${name}"
PRIVATE ssp::ssp fast_float doctest::doctest
)
target_compile_definitions(
"${name}"
PRIVATE DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN CMAKE_GITHUB_CI
)
add_test(NAME "${name}" COMMAND "${name}")
endforeach()

View File

@@ -4,10 +4,10 @@ test_sources = files([
'test_converter.cpp',
'test_parser.cpp',
'test_extractions.cpp',
'test_extractions_without_fast_float.cpp',
])
doctest_proj = subproject('doctest')
doctest_dep = doctest_proj.get_variable('doctest_dep')
doctest_dep = dependency('doctest')
test_exe = executable(
'test_ssp',

View File

@@ -2,23 +2,6 @@
#include <algorithm>
#include <ss/extract.hpp>
#define CHECK_FLOATING_CONVERSION(input, type) \
{ \
auto eps = std::numeric_limits<type>::min(); \
std::string s = #input; \
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
REQUIRE(t.has_value()); \
CHECK_LT(std::abs(t.value() - type(input)), eps); \
} \
{ \
/* check negative too */ \
auto eps = std::numeric_limits<type>::min(); \
auto s = std::string("-") + #input; \
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
REQUIRE(t.has_value()); \
CHECK_LT(std::abs(t.value() - type(-input)), eps); \
}
TEST_CASE("testing extract functions for floating point values") {
CHECK_FLOATING_CONVERSION(123.456, float);
CHECK_FLOATING_CONVERSION(123.456, double);
@@ -70,13 +53,6 @@ TEST_CASE("extract test functions for decimal values") {
CHECK_DECIMAL_CONVERSION(1234567891011, ull);
}
#define CHECK_INVALID_CONVERSION(input, type) \
{ \
std::string s = input; \
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
CHECK_FALSE(t.has_value()); \
}
TEST_CASE("extract test functions for numbers with invalid inputs") {
// negative unsigned value
CHECK_INVALID_CONVERSION("-1234", ul);
@@ -200,15 +176,6 @@ TEST_CASE("extract test functions for std::optional") {
}
}
#define REQUIRE_VARIANT(var, el, type) \
{ \
auto ptr = std::get_if<type>(&var); \
REQUIRE(ptr); \
REQUIRE_EQ(el, *ptr); \
}
#define CHECK_NOT_VARIANT(var, type) CHECK(!std::holds_alternative<type>(var));
TEST_CASE("extract test functions for std::variant") {
{
std::string s = "22";

View File

@@ -0,0 +1,132 @@
#include "test_helpers.hpp"
#include <algorithm>
#define SSP_DISABLE_FAST_FLOAT
#include <ss/extract.hpp>
TEST_CASE(
"testing extract functions for floating point values without fast float") {
CHECK_FLOATING_CONVERSION(123.456, float);
CHECK_FLOATING_CONVERSION(123.456, double);
CHECK_FLOATING_CONVERSION(69, float);
CHECK_FLOATING_CONVERSION(69, double);
CHECK_FLOATING_CONVERSION(420., float);
CHECK_FLOATING_CONVERSION(420., double);
CHECK_FLOATING_CONVERSION(0.123, float);
CHECK_FLOATING_CONVERSION(0.123, double);
CHECK_FLOATING_CONVERSION(123e4, float);
CHECK_FLOATING_CONVERSION(123e4, double);
}
TEST_CASE("extract test functions for numbers with invalid inputs without fast "
"float") {
// floating pint for int
CHECK_INVALID_CONVERSION("123.4", int);
// random input for float
CHECK_INVALID_CONVERSION("xxx1", float);
}
TEST_CASE("extract test functions for std::variant without fast float") {
{
std::string s = "22";
{
std::variant<int, 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);
}
{
std::variant<double, int, std::string> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, std::string);
REQUIRE_VARIANT(var, 22, double);
}
{
std::variant<std::string, double, int> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, double);
REQUIRE_VARIANT(var, "22", std::string);
}
{
std::variant<int> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
REQUIRE_VARIANT(var, 22, int);
}
}
{
std::string s = "22.2";
{
std::variant<int, 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, std::string);
REQUIRE_VARIANT(var, 22.2, double);
}
{
std::variant<double, int, std::string> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, std::string);
REQUIRE_VARIANT(var, 22.2, double);
}
{
std::variant<std::string, double, int> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, double);
REQUIRE_VARIANT(var, "22.2", std::string);
}
}
{
std::string s = "2.2.2";
{
std::variant<int, 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, double);
REQUIRE_VARIANT(var, "2.2.2", std::string);
}
{
std::variant<double, std::string, int> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, double);
REQUIRE_VARIANT(var, "2.2.2", std::string);
}
{
std::variant<std::string, double, int> var;
REQUIRE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
CHECK_NOT_VARIANT(var, int);
CHECK_NOT_VARIANT(var, double);
REQUIRE_VARIANT(var, "2.2.2", std::string);
}
{
std::variant<int, double> var;
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
REQUIRE_VARIANT(var, int{}, int);
CHECK_NOT_VARIANT(var, double);
}
{
std::variant<double, int> var;
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
REQUIRE_VARIANT(var, double{}, double);
CHECK_NOT_VARIANT(var, int);
}
{
std::variant<int> var;
REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var));
REQUIRE_VARIANT(var, int{}, int);
}
}
}

View File

@@ -1,6 +1,6 @@
#pragma once
#include <cstdlib>
#include <cstring>
#include <string>
#ifdef CMAKE_GITHUB_CI
#include <doctest/doctest.h>
@@ -9,40 +9,55 @@
#endif
struct buffer {
char* data_{nullptr};
std::string data_;
char* operator()(const char* data) {
if (data_) {
delete[] data_;
}
data_ = new char[strlen(data) + 1];
strcpy(data_, data);
return data_;
}
char *operator()(const std::string &data) {
data_ = data;
return data_.data();
}
char* append(const char* data) {
if (data_) {
char* new_data_ = new char[strlen(data_) + strlen(data) + 1];
strcpy(new_data_, data_);
strcat(new_data_, data);
delete[] data_;
data_ = new_data_;
return data_;
} else {
return operator()(data);
}
}
char *append(const std::string &data) {
data_ += data;
return data_.data();
}
char* append_overwrite_last(const char* data, size_t size) {
data_[strlen(data_) - size] = '\0';
return append(data);
}
~buffer() {
if (data_) {
delete[] data_;
}
}
char *append_overwrite_last(const std::string &data, size_t size) {
data_.resize(data_.size() - size);
return append(data);
}
};
[[maybe_unused]] inline buffer buff;
#define CHECK_FLOATING_CONVERSION(input, type) \
{ \
auto eps = std::numeric_limits<type>::min(); \
std::string s = #input; \
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
REQUIRE(t.has_value()); \
CHECK_LT(std::abs(t.value() - type(input)), eps); \
} \
{ \
/* check negative too */ \
auto eps = std::numeric_limits<type>::min(); \
auto s = std::string("-") + #input; \
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
REQUIRE(t.has_value()); \
CHECK_LT(std::abs(t.value() - type(-input)), eps); \
}
#define CHECK_INVALID_CONVERSION(input, type) \
{ \
std::string s = input; \
auto t = ss::to_num<type>(s.c_str(), s.c_str() + s.size()); \
CHECK_FALSE(t.has_value()); \
}
#define REQUIRE_VARIANT(var, el, type) \
{ \
auto ptr = std::get_if<type>(&var); \
REQUIRE(ptr); \
REQUIRE_EQ(el, *ptr); \
}
#define CHECK_NOT_VARIANT(var, type) CHECK(!std::holds_alternative<type>(var));

View File

@@ -283,7 +283,7 @@ TEST_CASE("parser test various cases") {
}
std::copy_if(data.begin(), data.end(), expected.begin(),
[](const X& x) { return x.i != excluded; });
[&](const X& x) { return x.i != excluded; });
CHECK_EQ(i, expected);
CHECK_EQ(i2, expected);
}
@@ -378,7 +378,7 @@ TEST_CASE("parser test composite conversion") {
p.try_next<int, int, double>(fail)
.or_else<test_struct>(fail)
.or_else<int, char, double>(
[](auto&& data) { CHECK_EQ(data, expectedData); })
[&](auto&& data) { CHECK_EQ(data, expectedData); })
.on_error(fail)
.or_else<test_tuple>(fail)
.values();
@@ -396,7 +396,7 @@ TEST_CASE("parser test composite conversion") {
constexpr static auto expectedData = std::tuple{10, 20, 11.1};
auto [d1, d2, d3, d4] =
p.try_next<int, int, double>([](auto& i1, auto i2, double d) {
p.try_next<int, int, double>([&](auto& i1, auto i2, double d) {
CHECK_EQ(std::tie(i1, i2, d), expectedData);
})
.on_error(fail)
@@ -564,8 +564,6 @@ TEST_CASE("parser test composite conversion") {
CHECK(p.eof());
}
size_t move_called = 0;
struct my_string {
char* data{nullptr};
@@ -580,12 +578,10 @@ struct my_string {
my_string& operator=(const my_string&) = delete;
my_string(my_string&& other) : data{other.data} {
move_called++;
other.data = nullptr;
}
my_string& operator=(my_string&& other) {
move_called++;
data = other.data;
return *this;
}
@@ -609,52 +605,6 @@ struct xyz {
}
};
TEST_CASE("parser test the moving of parsed values") {
size_t move_called_one_col;
{
unique_file_name f;
{
std::ofstream out{f.name};
out << "x" << std::endl;
}
ss::parser p{f.name, ","};
auto x = p.get_next<my_string>();
CHECK_LT(move_called, 3);
move_called_one_col = move_called;
move_called = 0;
}
unique_file_name f;
{
std::ofstream out{f.name};
out << "a,b,c" << std::endl;
}
{
ss::parser p{f.name, ","};
auto x = p.get_next<my_string, my_string, my_string>();
CHECK_LE(move_called, 3 * move_called_one_col);
move_called = 0;
}
{
ss::parser p{f.name, ","};
auto x = p.get_object<xyz, my_string, my_string, my_string>();
CHECK_LE(move_called, 6 * move_called_one_col);
move_called = 0;
}
{
ss::parser p{f.name, ","};
auto x = p.get_next<xyz>();
CHECK_LE(move_called, 6 * move_called_one_col);
move_called = 0;
}
}
TEST_CASE("parser test the moving of parsed composite values") {
// to compile is enough
return;

14
test/test_single_header.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
set -x
set -e
python3 script/single_header_generator.py > ssp.cpp
echo 'int main(){ ss::parser p{""}; p.get_next<int, float>(); return 0; }' \
>> ssp.cpp
g++ -std=c++17 ssp.cpp -o ssp.bin -Wall -Wextra
./ssp.bin
rm ssp.cpp ssp.bin