11 Commits

Author SHA1 Message Date
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
10 changed files with 3215 additions and 39 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

@@ -69,6 +69,10 @@ Bill (Heath) Gates 65 3.3
* [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", ","};
@@ -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

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

@@ -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))

2967
ssp.hpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ 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_proj = subproject('doctest')

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));

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