From 62055f03c728114ae04f0a40ddbd98edac2dc2be Mon Sep 17 00:00:00 2001 From: ado Date: Wed, 30 Mar 2022 18:14:30 +0200 Subject: [PATCH 1/9] add script to generate single header --- include/ss/extract.hpp | 28 +++++++++++++++++++++++++ script/single_header_generator.py | 34 +++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100755 script/single_header_generator.py diff --git a/include/ss/extract.hpp b/include/ss/extract.hpp index 2e9e6f0..52aa0f9 100644 --- a/include/ss/extract.hpp +++ b/include/ss/extract.hpp @@ -17,6 +17,9 @@ namespace ss { // number converters //////////////// +#define SSP_DISABLE_FAST_FLOAT +#ifndef SSP_DISABLE_FAST_FLOAT + template std::enable_if_t, std::optional> to_num( const char* const begin, const char* const end) { @@ -29,6 +32,31 @@ std::enable_if_t, std::optional> to_num( return ret; } +#else + +template +std::enable_if_t, std::optional> to_num( + const char* const begin, const char* const end) { + T ret; + try { + if constexpr (std::is_same_v) { + ret = std::stof(std::string{begin, end}); + } + if constexpr (std::is_same_v) { + ret = std::stod(std::string{begin, end}); + } + if constexpr (std::is_same_v) { + ret = std::stold(std::string{begin, end}); + } + } catch (...) { + return std::nullopt; + } + + return ret; +} + +#endif + inline std::optional from_char(char c) { if (c >= '0' && c <= '9') { return c - '0'; diff --git a/script/single_header_generator.py b/script/single_header_generator.py new file mode 100755 index 0000000..3916666 --- /dev/null +++ b/script/single_header_generator.py @@ -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 Date: Wed, 30 Mar 2022 18:35:10 +0200 Subject: [PATCH 2/9] add test_single_header.sh --- .gitignore | 2 ++ script/test_single_header.sh | 15 +++++++++++++++ test/test_single_header.sh | 14 ++++++++++++++ 3 files changed, 31 insertions(+) create mode 100755 script/test_single_header.sh create mode 100755 test/test_single_header.sh diff --git a/.gitignore b/.gitignore index eb880af..38a1e99 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ build/ hbuild/ subprojects/* !subprojects/*.wrap +ssp.cpp +ssp.bin diff --git a/script/test_single_header.sh b/script/test_single_header.sh new file mode 100755 index 0000000..61c3522 --- /dev/null +++ b/script/test_single_header.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +set -x +set -e + +python3 script/single_header_generator.py > ssp.cpp + +echo "" >> ssh.cpp +echo 'int main(){ ss::parser p{""}; p.get_next(); return 0; }' \ + >> ssp.cpp + +g++ -std=c++17 ssp.cpp -o ssp.bin -Wall -Wextra +./ssp.bin + +rm ssp.cpp ssp.bin diff --git a/test/test_single_header.sh b/test/test_single_header.sh new file mode 100755 index 0000000..0d12c59 --- /dev/null +++ b/test/test_single_header.sh @@ -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(); return 0; }' \ + >> ssp.cpp + +g++ -std=c++17 ssp.cpp -o ssp.bin -Wall -Wextra +./ssp.bin + +rm ssp.cpp ssp.bin From c214975ca041fe5ae3c9dc8cc428093e548c626b Mon Sep 17 00:00:00 2001 From: ado Date: Wed, 30 Mar 2022 19:54:50 +0200 Subject: [PATCH 3/9] add unit tests for conversion without the fast float library --- include/ss/extract.hpp | 23 ++-- test/meson.build | 1 + test/test_extractions.cpp | 33 ----- test/test_extractions_without_fast_float.cpp | 132 +++++++++++++++++++ test/test_helpers.hpp | 33 +++++ 5 files changed, 175 insertions(+), 47 deletions(-) create mode 100644 test/test_extractions_without_fast_float.cpp diff --git a/include/ss/extract.hpp b/include/ss/extract.hpp index 52aa0f9..ae2d81c 100644 --- a/include/ss/extract.hpp +++ b/include/ss/extract.hpp @@ -2,7 +2,6 @@ #include "type_traits.hpp" #include -#include #include #include #include @@ -11,13 +10,18 @@ #include #include +#ifndef SSP_DISABLE_FAST_FLOAT +#include +#else +#include +#endif + namespace ss { //////////////// // number converters //////////////// -#define SSP_DISABLE_FAST_FLOAT #ifndef SSP_DISABLE_FAST_FLOAT template @@ -38,20 +42,11 @@ template std::enable_if_t, std::optional> to_num( const char* const begin, const char* const end) { T ret; - try { - if constexpr (std::is_same_v) { - ret = std::stof(std::string{begin, end}); - } - if constexpr (std::is_same_v) { - ret = std::stod(std::string{begin, end}); - } - if constexpr (std::is_same_v) { - ret = std::stold(std::string{begin, end}); - } - } catch (...) { + auto [ptr, ec] = std::from_chars(begin, end, ret); + + if (ec != std::errc() || ptr != end) { return std::nullopt; } - return ret; } diff --git a/test/meson.build b/test/meson.build index a8596eb..e2d33b1 100644 --- a/test/meson.build +++ b/test/meson.build @@ -4,6 +4,7 @@ test_sources = files([ 'test_converter.cpp', 'test_parser.cpp', 'test_extractions.cpp', + 'test_extractions_without_fast_float.cpp', ]) doctest_proj = subproject('doctest') diff --git a/test/test_extractions.cpp b/test/test_extractions.cpp index 1650289..bda33b4 100644 --- a/test/test_extractions.cpp +++ b/test/test_extractions.cpp @@ -2,23 +2,6 @@ #include #include -#define CHECK_FLOATING_CONVERSION(input, type) \ - { \ - auto eps = std::numeric_limits::min(); \ - std::string s = #input; \ - auto t = ss::to_num(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::min(); \ - auto s = std::string("-") + #input; \ - auto t = ss::to_num(s.c_str(), s.c_str() + s.size()); \ - REQUIRE(t.has_value()); \ - CHECK_LT(std::abs(t.value() - type(-input)), eps); \ - } - TEST_CASE("testing extract functions for floating point values") { CHECK_FLOATING_CONVERSION(123.456, float); CHECK_FLOATING_CONVERSION(123.456, double); @@ -70,13 +53,6 @@ TEST_CASE("extract test functions for decimal values") { CHECK_DECIMAL_CONVERSION(1234567891011, ull); } -#define CHECK_INVALID_CONVERSION(input, type) \ - { \ - std::string s = input; \ - auto t = ss::to_num(s.c_str(), s.c_str() + s.size()); \ - CHECK_FALSE(t.has_value()); \ - } - TEST_CASE("extract test functions for numbers with invalid inputs") { // negative unsigned value CHECK_INVALID_CONVERSION("-1234", ul); @@ -200,15 +176,6 @@ TEST_CASE("extract test functions for std::optional") { } } -#define REQUIRE_VARIANT(var, el, type) \ - { \ - auto ptr = std::get_if(&var); \ - REQUIRE(ptr); \ - REQUIRE_EQ(el, *ptr); \ - } - -#define CHECK_NOT_VARIANT(var, type) CHECK(!std::holds_alternative(var)); - TEST_CASE("extract test functions for std::variant") { { std::string s = "22"; diff --git a/test/test_extractions_without_fast_float.cpp b/test/test_extractions_without_fast_float.cpp new file mode 100644 index 0000000..3f5cd40 --- /dev/null +++ b/test/test_extractions_without_fast_float.cpp @@ -0,0 +1,132 @@ +#include "test_helpers.hpp" +#include + +#define SSP_DISABLE_FAST_FLOAT +#include + +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 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 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 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 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 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 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 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 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 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 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 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 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 var; + REQUIRE_FALSE(ss::extract(s.c_str(), s.c_str() + s.size(), var)); + + REQUIRE_VARIANT(var, int{}, int); + } + } +} diff --git a/test/test_helpers.hpp b/test/test_helpers.hpp index 03f2750..06ea703 100644 --- a/test/test_helpers.hpp +++ b/test/test_helpers.hpp @@ -46,3 +46,36 @@ struct buffer { }; [[maybe_unused]] inline buffer buff; + +#define CHECK_FLOATING_CONVERSION(input, type) \ + { \ + auto eps = std::numeric_limits::min(); \ + std::string s = #input; \ + auto t = ss::to_num(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::min(); \ + auto s = std::string("-") + #input; \ + auto t = ss::to_num(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(s.c_str(), s.c_str() + s.size()); \ + CHECK_FALSE(t.has_value()); \ + } + +#define REQUIRE_VARIANT(var, el, type) \ + { \ + auto ptr = std::get_if(&var); \ + REQUIRE(ptr); \ + REQUIRE_EQ(el, *ptr); \ + } + +#define CHECK_NOT_VARIANT(var, type) CHECK(!std::holds_alternative(var)); From a2d72cdef3c699b026dd1e91d83b2dab58006e6a Mon Sep 17 00:00:00 2001 From: ado Date: Wed, 30 Mar 2022 19:56:57 +0200 Subject: [PATCH 4/9] remove unused script --- script/test_single_header.sh | 15 --------------- test/test_single_header.sh | 2 +- 2 files changed, 1 insertion(+), 16 deletions(-) delete mode 100755 script/test_single_header.sh diff --git a/script/test_single_header.sh b/script/test_single_header.sh deleted file mode 100755 index 61c3522..0000000 --- a/script/test_single_header.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/bin/bash - -set -x -set -e - -python3 script/single_header_generator.py > ssp.cpp - -echo "" >> ssh.cpp -echo 'int main(){ ss::parser p{""}; p.get_next(); return 0; }' \ - >> ssp.cpp - -g++ -std=c++17 ssp.cpp -o ssp.bin -Wall -Wextra -./ssp.bin - -rm ssp.cpp ssp.bin diff --git a/test/test_single_header.sh b/test/test_single_header.sh index 0d12c59..8a2bb91 100755 --- a/test/test_single_header.sh +++ b/test/test_single_header.sh @@ -1,6 +1,6 @@ #!/bin/bash -set -x +set -x set -e python3 script/single_header_generator.py > ssp.cpp From 9fa9edb24d762866c49755f71204194c5a075202 Mon Sep 17 00:00:00 2001 From: ado Date: Wed, 30 Mar 2022 20:11:55 +0200 Subject: [PATCH 5/9] add spp.hpp --- include/ss/extract.hpp | 1 - ssp.hpp | 2967 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 2967 insertions(+), 1 deletion(-) create mode 100644 ssp.hpp diff --git a/include/ss/extract.hpp b/include/ss/extract.hpp index ae2d81c..760aca8 100644 --- a/include/ss/extract.hpp +++ b/include/ss/extract.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include diff --git a/ssp.hpp b/ssp.hpp new file mode 100644 index 0000000..b49906b --- /dev/null +++ b/ssp.hpp @@ -0,0 +1,2967 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#define SSP_DISABLE_FAST_FLOAT + + +namespace ss { + +//////////////// +// tup merge/cat +//////////////// + +template +struct tup_cat; + +template +struct tup_cat, std::tuple> { + using type = std::tuple; +}; + +template +struct tup_cat> { + using type = std::tuple; +}; + +template +using tup_cat_t = typename tup_cat::type; + +//////////////// +// tup first/head +//////////////// + +template +struct left_of_impl; + +template +struct left_of_impl { + static_assert(N < 128, "recursion limit reached"); + static_assert(N != 0, "cannot take the whole tuple"); + using type = tup_cat_t::type>; +}; + +template +struct left_of_impl<0, T, Ts...> { + using type = std::tuple; +}; + +template +using left_of_t = typename left_of_impl::type; + +template +using first_t = typename left_of_impl::type; + +template +using head_t = typename left_of_impl<0, Ts...>::type; + +//////////////// +// tup tail/last +//////////////// + +template +struct right_of_impl; + +template +struct right_of_impl { + using type = typename right_of_impl::type; +}; + +template +struct right_of_impl<0, T, Ts...> { + using type = std::tuple; +}; + +template +using right_of_t = typename right_of_impl::type; + +template +using tail_t = typename right_of_impl<1, Ts...>::type; + +template +using last_t = typename right_of_impl::type; + +//////////////// +// apply trait +//////////////// + +template