31 Commits

Author SHA1 Message Date
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
21 changed files with 3295 additions and 149 deletions

2
.gitignore vendored
View File

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

View File

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

View File

@@ -53,22 +53,26 @@ Brian S. Wolfe 40 1.9
Bill (Heath) Gates 65 3.3 Bill (Heath) Gates 65 3.3
``` ```
# Features # Features
* [Works on any type](#Custom-conversions) * [Works on any type](#custom-conversions)
* Easy to use * Easy to use
* No exceptions * No exceptions
* [Works with headers](#Headers) * [Works with headers](#headers)
* [Works with quotes, escapes and spacings](#Setup) * [Works with quotes, escapes and spacings](#setup)
* [Works with values containing new lines](#Multiline) * [Works with values containing new lines](#multiline)
* [Columns and rows can be ignored](#Special-types) * [Columns and rows can be ignored](#special-types)
* Works with any type of delimiter * Works with any type of delimiter
* Can return whole objects composed of converted values * Can return whole objects composed of converted values
* [Descriptive error handling can be enabled](#Error-handling) * [Descriptive error handling can be enabled](#error-handling)
* [Restrictions can be added for each column](#Restrictions) * [Restrictions can be added for each column](#restrictions)
* [Works with `std::optional` and `std::variant`](#Special-types) * [Works with `std::optional` and `std::variant`](#special-types)
* Works with **`CRLF`** and **`LF`** * Works with **`CRLF`** and **`LF`**
* [Conversions can be chained if invalid](#Substitute-conversions) * [Conversions can be chained if invalid](#substitute-conversions)
* Fast * 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 # Installation
```shell ```shell
@@ -113,7 +117,7 @@ The header can be ignored using the **`ss::ignore_header`** [setup](#Setup) opti
```cpp ```cpp
ss::parser<ss::ignore_header> p{file_name}; 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 ```cpp
// ... // ...
ss::parser p{"students.csv", ","}; ss::parser p{"students.csv", ","};
@@ -374,7 +378,7 @@ if(std::holds_alternative<float>(grade)) {
``` ```
## Restrictions ## 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 ```cpp
// ss::ne makes sure that the name is not empty // ss::ne makes sure that the name is not empty
// ss::ir makes sure that the grade will be in range [0, 10] // ss::ir makes sure that the grade will be in range [0, 10]
@@ -468,7 +472,6 @@ The delimiter is " ", and the number of columns varies depending on which shape
```cpp ```cpp
ss::parser p{"shapes.txt", " "}; ss::parser p{"shapes.txt", " "};
if (!p.valid()) { if (!p.valid()) {
std::cout << p.error_msg() << std::endl;
exit(EXIT_FAILURE); exit(EXIT_FAILURE);
} }
@@ -510,7 +513,7 @@ while (!p.eof()) {
``` ```
It is quite hard to make an error this way since most things will be checked at compile time. 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: 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 ```cpp
@@ -628,6 +631,5 @@ revision = origin/master
``` ```
Then simply fetch the dependency and it is ready to be used: Then simply fetch the dependency and it is ready to be used:
```meson ```meson
ssp_sub = subproject('ssp') ssp_dep = dependency('ssp')
ssp_dep = ssp_sub.get_variable('ssp_dep')
``` ```

View File

@@ -102,7 +102,7 @@ class converter {
constexpr static auto string_error = setup<Matchers...>::string_error; constexpr static auto string_error = setup<Matchers...>::string_error;
constexpr static auto default_delimiter = ","; 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: public:
// parses line with given delimiter, returns a 'T' object created with // 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 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); return splitter_.resplit(new_line, new_size, delim);
} }

View File

@@ -2,21 +2,27 @@
#include "type_traits.hpp" #include "type_traits.hpp"
#include <cstring> #include <cstring>
#include <fast_float/fast_float.h>
#include <functional> #include <functional>
#include <limits> #include <limits>
#include <optional> #include <optional>
#include <stdexcept>
#include <string> #include <string>
#include <string_view> #include <string_view>
#include <variant> #include <variant>
#ifndef SSP_DISABLE_FAST_FLOAT
#include <fast_float/fast_float.h>
#else
#include <charconv>
#endif
namespace ss { namespace ss {
//////////////// ////////////////
// number converters // number converters
//////////////// ////////////////
#ifndef SSP_DISABLE_FAST_FLOAT
template <typename T> template <typename T>
std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num( std::enable_if_t<std::is_floating_point_v<T>, std::optional<T>> to_num(
const char* const begin, const char* const end) { 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; 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) { inline std::optional<short> from_char(char c) {
if (c >= '0' && c <= '9') { if (c >= '0' && c <= '9') {
return c - '0'; return c - '0';

View File

@@ -17,7 +17,7 @@ class parser {
constexpr static auto string_error = setup<Matchers...>::string_error; constexpr static auto string_error = setup<Matchers...>::string_error;
using multiline = typename setup<Matchers...>::multiline; 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 = constexpr static bool escaped_multiline_enabled =
multiline::enabled && setup<Matchers...>::escape::enabled; multiline::enabled && setup<Matchers...>::escape::enabled;
@@ -151,8 +151,8 @@ public:
template <bool get_object, typename T, typename... Ts> template <bool get_object, typename T, typename... Ts>
struct iterable { struct iterable {
struct iterator { struct iterator {
using value = using value = std::conditional_t<get_object, T,
ss::ternary_t<get_object, T, no_void_validator_tup_t<T, Ts...>>; no_void_validator_tup_t<T, Ts...>>;
iterator() : parser_{nullptr} { iterator() : parser_{nullptr} {
} }
@@ -623,7 +623,8 @@ private:
} }
} }
next_line_converter_.resplit(next_line_buffer_, size); next_line_converter_.resplit(next_line_buffer_, size,
delim_);
} }
} }

View File

@@ -111,8 +111,8 @@ struct get_matcher<Matcher, T, Ts...> {
static_assert(count_v<is_matcher, T, Ts...> <= 1, static_assert(count_v<is_matcher, T, Ts...> <= 1,
"the same matcher is cannot" "the same matcher is cannot"
"be defined multiple times"); "be defined multiple times");
using type = ternary_t<is_matcher<T>::value, T, using type = std::conditional_t<is_matcher<T>::value, T,
typename get_matcher<Matcher, Ts...>::type>; typename get_matcher<Matcher, Ts...>::type>;
}; };
template <template <char...> class Matcher> template <template <char...> class Matcher>
@@ -149,8 +149,8 @@ struct get_multiline;
template <typename T, typename... Ts> template <typename T, typename... Ts>
struct get_multiline<T, Ts...> { struct get_multiline<T, Ts...> {
using type = ternary_t<is_instance_of_multiline<T>::value, T, using type = std::conditional_t<is_instance_of_multiline<T>::value, T,
typename get_multiline<Ts...>::type>; typename get_multiline<Ts...>::type>;
}; };
template <> template <>
@@ -227,8 +227,10 @@ public:
using quote = get_matcher_t<quote, Ts...>; using quote = get_matcher_t<quote, Ts...>;
using escape = get_matcher_t<escape, Ts...>; using escape = get_matcher_t<escape, Ts...>;
using trim_left = ternary_t<trim_all::enabled, trim_all, trim_left_only>; using trim_left =
using trim_right = ternary_t<trim_all::enabled, trim_all, trim_right_only>; 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...>; using multiline = get_multiline_t<Ts...>;
constexpr static bool string_error = (count_string_error == 1); 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 string_error = setup<Ts...>::string_error;
constexpr static auto is_const_line = !quote::enabled && !escape::enabled; 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: 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 { bool valid() const {
if constexpr (string_error) { if constexpr (string_error) {

View File

@@ -357,26 +357,6 @@ struct is_instance_of<Template, Template<Ts...>> {
template <template <typename...> class Template, typename... Ts> template <template <typename...> class Template, typename... Ts>
constexpr bool is_instance_of_v = is_instance_of<Template, Ts...>::value; 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 // tuple to struct
//////////////// ////////////////

View File

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

View File

@@ -5,7 +5,7 @@ BUILD_TYPE=Debug
set -eux 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 \ cmake -S doctest -B doctest/build \
-D CMAKE_BUILD_TYPE=${BUILD_TYPE} \ -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))

2946
ssp.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

@@ -2,23 +2,6 @@
#include <algorithm> #include <algorithm>
#include <ss/extract.hpp> #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") { TEST_CASE("testing extract functions for floating point values") {
CHECK_FLOATING_CONVERSION(123.456, float); CHECK_FLOATING_CONVERSION(123.456, float);
CHECK_FLOATING_CONVERSION(123.456, double); CHECK_FLOATING_CONVERSION(123.456, double);
@@ -70,13 +53,6 @@ TEST_CASE("extract test functions for decimal values") {
CHECK_DECIMAL_CONVERSION(1234567891011, ull); 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") { TEST_CASE("extract test functions for numbers with invalid inputs") {
// negative unsigned value // negative unsigned value
CHECK_INVALID_CONVERSION("-1234", ul); 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") { TEST_CASE("extract test functions for std::variant") {
{ {
std::string s = "22"; 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

@@ -46,3 +46,36 @@ struct buffer {
}; };
[[maybe_unused]] inline buffer buff; [[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

@@ -610,8 +610,6 @@ struct xyz {
}; };
TEST_CASE("parser test the moving of parsed values") { TEST_CASE("parser test the moving of parsed values") {
size_t move_called_one_col;
{ {
unique_file_name f; unique_file_name f;
{ {
@@ -621,8 +619,7 @@ TEST_CASE("parser test the moving of parsed values") {
ss::parser p{f.name, ","}; ss::parser p{f.name, ","};
auto x = p.get_next<my_string>(); auto x = p.get_next<my_string>();
CHECK_LT(move_called, 3); CHECK_LE(move_called, 1);
move_called_one_col = move_called;
move_called = 0; move_called = 0;
} }
@@ -636,21 +633,21 @@ TEST_CASE("parser test the moving of parsed values") {
ss::parser p{f.name, ","}; ss::parser p{f.name, ","};
auto x = p.get_next<my_string, my_string, my_string>(); auto x = p.get_next<my_string, my_string, my_string>();
CHECK_LE(move_called, 3 * move_called_one_col); CHECK_LE(move_called, 3);
move_called = 0; move_called = 0;
} }
{ {
ss::parser p{f.name, ","}; ss::parser p{f.name, ","};
auto x = p.get_object<xyz, my_string, my_string, my_string>(); auto x = p.get_object<xyz, my_string, my_string, my_string>();
CHECK_LE(move_called, 6 * move_called_one_col); CHECK_LE(move_called, 6);
move_called = 0; move_called = 0;
} }
{ {
ss::parser p{f.name, ","}; ss::parser p{f.name, ","};
auto x = p.get_next<xyz>(); auto x = p.get_next<xyz>();
CHECK_LE(move_called, 6 * move_called_one_col); CHECK_LE(move_called, 6);
move_called = 0; move_called = 0;
} }
} }

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