Merge pull request #16 from red0124/feature/single_header

Feature/single header
This commit is contained in:
red0124 2022-03-30 20:26:51 +02:00 committed by GitHub
commit d58644fd67
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 3213 additions and 37 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
@ -78,8 +82,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

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