123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- #include "simple/io/open.h"
- #include "simple/io/write.h"
- #include "simple/io/read.h"
- #include "simple/io/read_iterator.h"
- #include "simple/io/resize.h"
- #include "simple/io/execute.h"
- #include "simple/io/seek.h"
- #include <array>
- // aaaaaaaaaaaaaaaa
- #include "simple/support/bits.hpp"
- #include "simple/support/algorithm/traits.hpp"
- #include "simple/support/algorithm/utils.hpp"
- #include "simple/support/algorithm/mismatch.hpp"
- #include <charconv>
- using namespace std::literals;
- using namespace simple;
- using namespace io;
- template <typename B, std::enable_if_t<std::is_same_v<B,bool>>* = nullptr>
- [[nodiscard]] std::string get_message(const B& b)
- {
- return "bool: "s + (b ? "true" : "false");
- }
- namespace can_to_string_helper
- {
- using std::to_string;
- template <typename T, typename Enabled = std::nullptr_t>
- struct check : std::false_type {};
- template <typename T>
- struct check<T, decltype(void(to_string(std::declval<T>())), nullptr)>
- : std::true_type {};
- } // namespace can_to_string_helper
- template <typename T>
- struct can_to_string : can_to_string_helper::check<T> {};
- template <typename T>
- constexpr bool can_to_string_v = can_to_string<T>::value;
- template <typename R, std::enable_if_t<support::is_template_instance_v<support::range,R>>* = nullptr>
- [[nodiscard]] std::string get_message(const R& r)
- {
- using value_type = typename R::bounds_type::value_type;
- // TODO if pointer and null don't size, cause that UBish
- std::string message = "simple::support::range<[typeid]"s + typeid(value_type).name() + "[typeid]>: "s
- + (r.valid() ? "valid" : "invalid");
- if constexpr (support::is_range_v<R>)
- {
- // TODO: handle well known iterators
- // at least contiguous container iterators can be converted to pointers in C++20 to_address
- // naive linked list would have nullptr as end iterator, which could also be useful info
- // the node addresses as well could be more or less meaningful dependent on the allocator
- if constexpr (std::is_pointer_v<value_type>)
- {
- // convert to integer so that it's safe to work with
- support::range ir{
- reinterpret_cast<std::uintptr_t>(r.begin()),
- reinterpret_cast<std::uintptr_t>(r.end())
- };
- // now this is not UB, even if the pointers are bogus
- message += " | size " + std::to_string(ir.upper() - ir.lower());
- // and to print had to convert to integer anyway
- std::string ptr;
- auto min_bit_count = [](auto x){ return x == 0 ? 1 : support::bit_count(x) - support::count_leading_zeros(x); };
- auto ptrstr = [&](auto x)
- {
- auto ptr_bit_count = min_bit_count(x);
- ptr.resize(ptr_bit_count / 4 + ((ptr_bit_count % 4) != 0));
- std::to_chars(ptr.data(), ptr.data() + ptr.size(), x, 16);
- };
- ptrstr(ir.lower());
- message += " | bounds [" + ptr;
- ptrstr(ir.upper());
- message += ", " + ptr + ")";
- }
- }
- else // not an iterable range, assume arithmetic
- {
- message += " | size " + std::to_string(r.upper() - r.lower());
- using std::to_string;
- if constexpr (can_to_string_v<value_type>)
- message += " | bounds [" + to_string(r.lower()) + ", " + to_string(r.upper()) + ")";
- }
- message += " |";
- return message;
- }
- template <typename A, std::enable_if_t<std::is_arithmetic_v<A> && not std::is_same_v<A,bool>>* = nullptr>
- [[nodiscard]] std::string get_message(const A& a)
- {
- return "Builtin arithmetic ([typeid]"s + typeid(a).name() + "[typeid]): " + std::to_string(a);
- }
- template <typename StdStr, std::enable_if_t<std::is_same_v<StdStr, std::string>>* = nullptr>
- [[nodiscard]] std::string get_message(const StdStr& str)
- {
- // TODO: limit string size, huge strings are not useful as messages
- // and yet you do want a reasonable amount of structured text like say JSON
- return "std::string: size "s + std::to_string(str.size()) + " | " + str;
- }
- template <typename... Ts>
- [[nodiscard]] std::string get_message(const meta::list<Ts...>&)
- {
- return "meta::list: size "s + std::to_string(sizeof...(Ts)) + ((" | " + get_message(Ts{})) + ...);
- }
- template <typename T, typename Enabled = std::nullptr_t>
- struct can_get_message : std::false_type {};
- template <typename T>
- struct can_get_message<T, decltype(void(get_message(std::declval<T>())), nullptr)>
- : std::true_type {};
- template <typename T>
- constexpr bool can_get_message_v = can_get_message<T>::value;
- template <typename T, std::enable_if_t<not can_get_message_v<T>>* = nullptr>
- [[nodiscard]] std::string get_message(const T& t)
- {
- // TODO: if trivially copyable show byte representation in hex?
- // TODO: typeid is rtti and not super readable, might be better to just print __PRETTY_FUNCTION__ (c++20 function_name)
- return std::string("[typeid]") + typeid(t).name() + "[typeid]";
- };
- template <typename Error, typename F = std::nullptr_t, typename... Args>
- class exception_wrapper : public std::runtime_error, public Error
- {
- public:
- std::tuple<Args...> args;
- F f = nullptr;
- exception_wrapper(Error error, F&& f, Args&&... args) :
- std::runtime_error(
- "Function: " + get_message(f) + '\n' +
- (("Argument: " + get_message(args) + '\n') + ... + ""s) +
- "Error: " + get_message(error)),
- Error(std::move(error)),
- args{std::forward<Args>(args)...},
- f(std::forward<F>(f))
- {}
- exception_wrapper(Error error) :
- std::runtime_error("Error: " + get_message(error)),
- Error(std::move(error))
- {}
- };
- template <typename Error, typename F, typename... Args>
- exception_wrapper(Error error, F&& f, Args&&... args) -> exception_wrapper<Error, F, Args...>;
- template <std::size_t index = 0, typename Variant = void,
- std::enable_if_t<support::is_template_instance_v<std::variant, Variant>>* = nullptr>
- // TODO: [[nodiscard]] unless the required alternative is monostate
- decltype(auto) require(Variant&& var)
- {
- // static_assert(first variant alternative does not repeat);
- return std::visit([](auto&& a) -> decltype(std::get<index>(std::forward<Variant>(var)))
- {
- if constexpr (std::is_same_v<
- std::variant_alternative_t<index, Variant>,
- std::remove_reference_t<decltype(a)>>
- ) return std::forward<decltype(a)>(a);
- else throw exception_wrapper(a);
- }, std::forward<Variant>(var));
- };
- template <std::size_t index = 0, typename F = void, typename... Args,
- typename Variant = std::invoke_result_t<F, Args...>,
- std::enable_if_t<support::is_template_instance_v<std::variant, std::remove_reference_t<Variant>>>* = nullptr
- >
- // TODO: [[nodiscard]] unless the required alternative is monostate
- decltype(auto) require(F&& f, Args&&... args)
- {
- // static_assert(first variant alternative does not repeat);
- return std::visit([fargs = std::forward_as_tuple(std::forward<F>(f), std::forward<Args>(args)...)](auto&& a)
- -> std::variant_alternative_t<index, Variant>
- {
- if constexpr (std::is_same_v<
- std::variant_alternative_t<index, Variant>,
- std::remove_reference_t<decltype(a)>>
- ) return std::forward<decltype(a)>(a);
- else throw std::apply([a](auto&&... eargs)
- // TODO if earg is move only and passed as lvalue, only send off its type with the exception, leave the value untouched... currently cryptic compiler error instead
- { return exception_wrapper(a, std::forward<decltype(eargs)>(eargs)...); },
- fargs);
- }, std::invoke(std::forward<F>(f), std::forward<Args>(args)...));
- };
- // TODO: request(F, Args...) noexcept - return first variant arg with index >0 or F(Args...)
- // assert that only one arg is error (index>0), otherwise have to tuple and that can get out of hand quick, up to the user to reconcile independent operations
- int main() try
- {
- auto sf = require( open_f(meta::list(mode::write, mode::create), "helotherefile") );
- auto data = as_byte_view("uomo");
- // we offer up sf but it won't be taken unless error occurs in which case it'll be sent off with the exception
- // in general std move should really be called std::offer
- assert(( require( write_f, std::move(sf), data ) == data.end() ));
- assert(( require( write_f, std::move(sf), data ) == data.end() ));
- assert(( require( write_f, std::move(sf), data ) == data.end() ));
- std::array<std::byte, 10> buf{};
- std::byte* red = require( read_f, standard_input, as_byte_range(buf) );
- while(red)
- {
- assert(( require( write_f, standard_output, byte_view{buf.data(), red} ) == red ));
- red = require( read_f, standard_input, as_byte_range(buf));
- }
- const char* expected = "uomouomouomo";
- auto expected_data = as_byte_view(expected);
- auto rd = require( open_f, meta::list(mode::read), "helotherefile"s );
- assert(( support::mismatch(read_iterator(rd, as_byte_range(buf)), read_iterator(),
- expected_data.begin(), expected_data.end()) == std::pair{read_iterator(), expected_data.end()} ));
- assert( require( size_f, rd ) == 12 );
- require( resize_f, sf, 3 );
- assert( require( size_f, rd ) == 3 );
- require( seek_f, rd, 0 );
- auto resized = as_byte_view("uom");
- assert(( support::mismatch(read_iterator(rd, as_byte_range(buf)), read_iterator(),
- resized.begin(), resized.end()) == std::pair{read_iterator(), resized.end()} ));
- {
- auto expected_echo = as_byte_view("uomouomouomo\n");
- auto [process, out] = require( execute_f, meta::list{standard_output}, "echo"s, "echo"s, expected );
- assert(( support::mismatch(read_iterator(out, as_byte_range(buf)), read_iterator(),
- expected_echo.begin(), expected_echo.end()) == std::pair{read_iterator(), expected_echo.end()} ));
- }
- // TODO: remove them test files
- return 0;
- }
- catch(...)
- {
- perror("omaaaagooot");
- throw;
- }
|