mirror of
https://github.com/red0124/ssp.git
synced 2025-12-15 22:29:55 +01:00
Compare commits
61 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
57abdb3923 | ||
| 27ef7f2e21 | |||
| f58091cf02 | |||
|
|
78c75abec7 | ||
| 76091263f2 | |||
| d99cba274e | |||
| e70fc05646 | |||
| 21d1096edf | |||
| 72b74a3b64 | |||
| b7f6009eb9 | |||
| bdfd896861 | |||
| d6bc8086b2 | |||
| f762736da2 | |||
| 739077a8db | |||
| 3dcb2ce2ef | |||
| e4d9e10ac3 | |||
| a98742945b | |||
| 5d6c2c4af5 | |||
| 58857fff2d | |||
| e6a7e09975 | |||
| 705ce422f0 | |||
| 75f5ee2a55 | |||
| 6baeb2d598 | |||
| 7b1f49d304 | |||
| 515ddad997 | |||
| fd39b5eef2 | |||
|
|
77185cb366 | ||
| ad991d6a7d | |||
|
|
304ca6ef0f | ||
| 103ff33f21 | |||
|
|
8b928de086 | ||
| 6edce88d79 | |||
|
|
6196f7796b | ||
| 5672aa635e | |||
| 3eefac93b1 | |||
| 774f452689 | |||
| 448066b173 | |||
|
|
a7eca6064a | ||
| a4ecbd4dc8 | |||
|
|
a9e9783e6a | ||
| 04edf1e532 | |||
|
|
8dcb69aa2c | ||
| db2a72c18b | |||
|
|
0e28a06799 | ||
| 2218b01b81 | |||
| f777b04eb8 | |||
| 7831bbd735 | |||
| 6efb39b2db | |||
| f2b49e6d6c | |||
| 5cb3c65b24 | |||
|
|
d58644fd67 | ||
| 56447eb1d6 | |||
| 86d732e743 | |||
| 031ab5f7fc | |||
| 420625b25c | |||
| 9fa9edb24d | |||
| a2d72cdef3 | |||
| c214975ca0 | |||
| d328f7d59d | |||
| 62055f03c7 | |||
| 999992e579 |
63
.github/workflows/win-msvc.yml
vendored
Normal file
63
.github/workflows/win-msvc.yml
vendored
Normal 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
2
.gitignore
vendored
@@ -6,3 +6,5 @@ build/
|
|||||||
hbuild/
|
hbuild/
|
||||||
subprojects/*
|
subprojects/*
|
||||||
!subprojects/*.wrap
|
!subprojects/*.wrap
|
||||||
|
ssp.cpp
|
||||||
|
ssp.bin
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
47
README.md
47
README.md
@@ -8,11 +8,12 @@
|
|||||||
```
|
```
|
||||||
|
|
||||||
[](https://opensource.org/licenses/MIT)
|
[](https://opensource.org/licenses/MIT)
|
||||||

|
[](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-gcc.yml)
|
||||||

|
[](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-clang.yml)
|
||||||

|
[](https://github.com/red0124/ssp/actions/workflows/ubuntu-latest-icc.yml)
|
||||||

|
[](https://github.com/red0124/ssp/actions/workflows/win-msys2-gcc.yml)
|
||||||

|
[](https://github.com/red0124/ssp/actions/workflows/win-msys2-clang.yml)
|
||||||
|
[](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)
|
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
|
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
|
||||||
@@ -78,8 +83,8 @@ $ cmake --configure .
|
|||||||
$ sudo make install
|
$ sudo make install
|
||||||
```
|
```
|
||||||
|
|
||||||
*Note, this will also install the fast_float library*
|
*Note, this will also install the fast_float library*
|
||||||
The library supports [CMake](#Cmake) and [meson](#Meson) build systems
|
The library supports [CMake](#Cmake) and [meson](#Meson) build systems
|
||||||
|
|
||||||
# Usage
|
# Usage
|
||||||
|
|
||||||
@@ -113,7 +118,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 +379,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 +473,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 +514,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 +632,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')
|
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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} {
|
||||||
}
|
}
|
||||||
@@ -600,6 +600,8 @@ private:
|
|||||||
|
|
||||||
if constexpr (quoted_multiline_enabled) {
|
if constexpr (quoted_multiline_enabled) {
|
||||||
while (unterminated_quote()) {
|
while (unterminated_quote()) {
|
||||||
|
size -= next_line_converter_.size_shifted();
|
||||||
|
|
||||||
if (multiline_limit_reached(limit)) {
|
if (multiline_limit_reached(limit)) {
|
||||||
return true;
|
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) {
|
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_) {
|
if (crlf_) {
|
||||||
std::copy_n("\r\n\0", 3, buffer + string_end);
|
std::copy_n("\r\n\0", 3, buffer + string_end);
|
||||||
string_end += 2;
|
string_end += 2;
|
||||||
|
|||||||
@@ -109,10 +109,10 @@ struct get_matcher<Matcher, T, Ts...> {
|
|||||||
struct is_matcher : is_instance_of_matcher<U, Matcher> {};
|
struct is_matcher : is_instance_of_matcher<U, Matcher> {};
|
||||||
|
|
||||||
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 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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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
|
||||||
////////////////
|
////////////////
|
||||||
|
|||||||
19
meson.build
19
meson.build
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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} \
|
||||||
|
|||||||
34
script/single_header_generator.py
Executable file
34
script/single_header_generator.py
Executable 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))
|
||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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";
|
||||||
|
|||||||
132
test/test_extractions_without_fast_float.cpp
Normal file
132
test/test_extractions_without_fast_float.cpp
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
#include <cstdlib>
|
|
||||||
#include <cstring>
|
#include <string>
|
||||||
|
|
||||||
#ifdef CMAKE_GITHUB_CI
|
#ifdef CMAKE_GITHUB_CI
|
||||||
#include <doctest/doctest.h>
|
#include <doctest/doctest.h>
|
||||||
@@ -9,40 +9,55 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
struct buffer {
|
struct buffer {
|
||||||
char* data_{nullptr};
|
std::string data_;
|
||||||
|
|
||||||
char* operator()(const char* data) {
|
char *operator()(const std::string &data) {
|
||||||
if (data_) {
|
data_ = data;
|
||||||
delete[] data_;
|
return data_.data();
|
||||||
}
|
}
|
||||||
data_ = new char[strlen(data) + 1];
|
|
||||||
strcpy(data_, data);
|
|
||||||
return data_;
|
|
||||||
}
|
|
||||||
|
|
||||||
char* append(const char* data) {
|
char *append(const std::string &data) {
|
||||||
if (data_) {
|
data_ += data;
|
||||||
char* new_data_ = new char[strlen(data_) + strlen(data) + 1];
|
return data_.data();
|
||||||
strcpy(new_data_, data_);
|
}
|
||||||
strcat(new_data_, data);
|
|
||||||
delete[] data_;
|
|
||||||
data_ = new_data_;
|
|
||||||
return data_;
|
|
||||||
} else {
|
|
||||||
return operator()(data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
char* append_overwrite_last(const char* data, size_t size) {
|
char *append_overwrite_last(const std::string &data, size_t size) {
|
||||||
data_[strlen(data_) - size] = '\0';
|
data_.resize(data_.size() - size);
|
||||||
return append(data);
|
return append(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
~buffer() {
|
|
||||||
if (data_) {
|
|
||||||
delete[] data_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
[[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));
|
||||||
|
|||||||
@@ -283,7 +283,7 @@ TEST_CASE("parser test various cases") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::copy_if(data.begin(), data.end(), expected.begin(),
|
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(i, expected);
|
||||||
CHECK_EQ(i2, expected);
|
CHECK_EQ(i2, expected);
|
||||||
}
|
}
|
||||||
@@ -378,7 +378,7 @@ TEST_CASE("parser test composite conversion") {
|
|||||||
p.try_next<int, int, double>(fail)
|
p.try_next<int, int, double>(fail)
|
||||||
.or_else<test_struct>(fail)
|
.or_else<test_struct>(fail)
|
||||||
.or_else<int, char, double>(
|
.or_else<int, char, double>(
|
||||||
[](auto&& data) { CHECK_EQ(data, expectedData); })
|
[&](auto&& data) { CHECK_EQ(data, expectedData); })
|
||||||
.on_error(fail)
|
.on_error(fail)
|
||||||
.or_else<test_tuple>(fail)
|
.or_else<test_tuple>(fail)
|
||||||
.values();
|
.values();
|
||||||
@@ -396,7 +396,7 @@ TEST_CASE("parser test composite conversion") {
|
|||||||
constexpr static auto expectedData = std::tuple{10, 20, 11.1};
|
constexpr static auto expectedData = std::tuple{10, 20, 11.1};
|
||||||
|
|
||||||
auto [d1, d2, d3, d4] =
|
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);
|
CHECK_EQ(std::tie(i1, i2, d), expectedData);
|
||||||
})
|
})
|
||||||
.on_error(fail)
|
.on_error(fail)
|
||||||
@@ -564,8 +564,6 @@ TEST_CASE("parser test composite conversion") {
|
|||||||
CHECK(p.eof());
|
CHECK(p.eof());
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t move_called = 0;
|
|
||||||
|
|
||||||
struct my_string {
|
struct my_string {
|
||||||
char* data{nullptr};
|
char* data{nullptr};
|
||||||
|
|
||||||
@@ -580,12 +578,10 @@ struct my_string {
|
|||||||
my_string& operator=(const my_string&) = delete;
|
my_string& operator=(const my_string&) = delete;
|
||||||
|
|
||||||
my_string(my_string&& other) : data{other.data} {
|
my_string(my_string&& other) : data{other.data} {
|
||||||
move_called++;
|
|
||||||
other.data = nullptr;
|
other.data = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
my_string& operator=(my_string&& other) {
|
my_string& operator=(my_string&& other) {
|
||||||
move_called++;
|
|
||||||
data = other.data;
|
data = other.data;
|
||||||
return *this;
|
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") {
|
TEST_CASE("parser test the moving of parsed composite values") {
|
||||||
// to compile is enough
|
// to compile is enough
|
||||||
return;
|
return;
|
||||||
|
|||||||
14
test/test_single_header.sh
Executable file
14
test/test_single_header.sh
Executable 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
|
||||||
Reference in New Issue
Block a user