write_something.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. #include "simple/io/open.h"
  2. #include "simple/io/write.h"
  3. #include "simple/io/read.h"
  4. #include "simple/io/read_iterator.h"
  5. #include "simple/io/resize.h"
  6. #include "simple/io/execute.h"
  7. #include "simple/io/seek.h"
  8. #include <array>
  9. // aaaaaaaaaaaaaaaa
  10. #include "simple/support/bits.hpp"
  11. #include "simple/support/algorithm/traits.hpp"
  12. #include "simple/support/algorithm/utils.hpp"
  13. #include "simple/support/algorithm/mismatch.hpp"
  14. #include "simple/support/type_traits/is_template_instance.hpp"
  15. #include <charconv>
  16. using namespace std::literals;
  17. using namespace simple;
  18. using namespace io;
  19. template <typename B, std::enable_if_t<std::is_same_v<B,bool>>* = nullptr>
  20. [[nodiscard]] std::string get_message(const B& b)
  21. {
  22. return "bool: "s + (b ? "true" : "false");
  23. }
  24. namespace can_to_string_helper
  25. {
  26. using std::to_string;
  27. template <typename T, typename Enabled = std::nullptr_t>
  28. struct check : std::false_type {};
  29. template <typename T>
  30. struct check<T, decltype(void(to_string(std::declval<T>())), nullptr)>
  31. : std::true_type {};
  32. } // namespace can_to_string_helper
  33. template <typename T>
  34. struct can_to_string : can_to_string_helper::check<T> {};
  35. template <typename T>
  36. constexpr bool can_to_string_v = can_to_string<T>::value;
  37. template <typename R, std::enable_if_t<support::is_template_instance_v<support::range,R>>* = nullptr>
  38. [[nodiscard]] std::string get_message(const R& r)
  39. {
  40. using value_type = typename R::bounds_type::value_type;
  41. // TODO if pointer and null don't size, cause that UBish
  42. std::string message = "simple::support::range<[typeid]"s + typeid(value_type).name() + "[typeid]>: "s
  43. + (r.valid() ? "valid" : "invalid");
  44. if constexpr (support::is_range_v<R>)
  45. {
  46. // TODO: handle well known iterators
  47. // at least contiguous container iterators can be converted to pointers in C++20 to_address
  48. // naive linked list would have nullptr as end iterator, which could also be useful info
  49. // the node addresses as well could be more or less meaningful dependent on the allocator
  50. if constexpr (std::is_pointer_v<value_type>)
  51. {
  52. // convert to integer so that it's safe to work with
  53. support::range ir{
  54. reinterpret_cast<std::uintptr_t>(r.begin()),
  55. reinterpret_cast<std::uintptr_t>(r.end())
  56. };
  57. // now this is not UB, even if the pointers are bogus
  58. message += " | size " + std::to_string(ir.upper() - ir.lower());
  59. // and to print had to convert to integer anyway
  60. std::string ptr;
  61. auto min_bit_count = [](auto x){ return x == 0 ? 1 : support::bit_count(x) - support::count_leading_zeros(x); };
  62. auto ptrstr = [&](auto x)
  63. {
  64. auto ptr_bit_count = min_bit_count(x);
  65. ptr.resize(ptr_bit_count / 4 + ((ptr_bit_count % 4) != 0));
  66. std::to_chars(ptr.data(), ptr.data() + ptr.size(), x, 16);
  67. };
  68. ptrstr(ir.lower());
  69. message += " | bounds [" + ptr;
  70. ptrstr(ir.upper());
  71. message += ", " + ptr + ")";
  72. }
  73. }
  74. else // not an iterable range, assume arithmetic
  75. {
  76. message += " | size " + std::to_string(r.upper() - r.lower());
  77. using std::to_string;
  78. if constexpr (can_to_string_v<value_type>)
  79. message += " | bounds [" + to_string(r.lower()) + ", " + to_string(r.upper()) + ")";
  80. }
  81. message += " |";
  82. return message;
  83. }
  84. template <typename A, std::enable_if_t<std::is_arithmetic_v<A> && not std::is_same_v<A,bool>>* = nullptr>
  85. [[nodiscard]] std::string get_message(const A& a)
  86. {
  87. return "Builtin arithmetic ([typeid]"s + typeid(a).name() + "[typeid]): " + std::to_string(a);
  88. }
  89. template <typename StdStr, std::enable_if_t<std::is_same_v<StdStr, std::string>>* = nullptr>
  90. [[nodiscard]] std::string get_message(const StdStr& str)
  91. {
  92. // TODO: limit string size, huge strings are not useful as messages
  93. // and yet you do want a reasonable amount of structured text like say JSON
  94. return "std::string: size "s + std::to_string(str.size()) + " | " + str;
  95. }
  96. template <typename... Ts>
  97. [[nodiscard]] std::string get_message(const meta::list<Ts...>&)
  98. {
  99. return "meta::list: size "s + std::to_string(sizeof...(Ts)) + ((" | " + get_message(Ts{})) + ...);
  100. }
  101. template <typename T, typename Enabled = std::nullptr_t>
  102. struct can_get_message : std::false_type {};
  103. template <typename T>
  104. struct can_get_message<T, decltype(void(get_message(std::declval<T>())), nullptr)>
  105. : std::true_type {};
  106. template <typename T>
  107. constexpr bool can_get_message_v = can_get_message<T>::value;
  108. template <typename T, std::enable_if_t<not can_get_message_v<T>>* = nullptr>
  109. [[nodiscard]] std::string get_message(const T& t)
  110. {
  111. // TODO: if trivially copyable show byte representation in hex?
  112. // TODO: typeid is rtti and not super readable, might be better to just print __PRETTY_FUNCTION__ (c++20 function_name)
  113. return std::string("[typeid]") + typeid(t).name() + "[typeid]";
  114. };
  115. template <typename Error, typename F = std::nullptr_t, typename... Args>
  116. class exception_wrapper : public std::runtime_error, public Error
  117. {
  118. public:
  119. std::tuple<Args...> args;
  120. F f = nullptr;
  121. exception_wrapper(Error error, F&& f, Args&&... args) :
  122. std::runtime_error(
  123. "Function: " + get_message(f) + '\n' +
  124. (("Argument: " + get_message(args) + '\n') + ... + ""s) +
  125. "Error: " + get_message(error)),
  126. Error(std::move(error)),
  127. args{std::forward<Args>(args)...},
  128. f(std::forward<F>(f))
  129. {}
  130. exception_wrapper(Error error) :
  131. std::runtime_error("Error: " + get_message(error)),
  132. Error(std::move(error))
  133. {}
  134. };
  135. template <typename Error, typename F, typename... Args>
  136. exception_wrapper(Error error, F&& f, Args&&... args) -> exception_wrapper<Error, F, Args...>;
  137. template <std::size_t index = 0, typename Variant = void,
  138. std::enable_if_t<support::is_template_instance_v<std::variant, Variant>>* = nullptr>
  139. // TODO: [[nodiscard]] unless the required alternative is monostate
  140. decltype(auto) require(Variant&& var)
  141. {
  142. // static_assert(first variant alternative does not repeat);
  143. return std::visit([](auto&& a) -> decltype(std::get<index>(std::forward<Variant>(var)))
  144. {
  145. if constexpr (std::is_same_v<
  146. std::variant_alternative_t<index, Variant>,
  147. std::remove_reference_t<decltype(a)>>
  148. ) return std::forward<decltype(a)>(a);
  149. else throw exception_wrapper(a);
  150. }, std::forward<Variant>(var));
  151. };
  152. template <std::size_t index = 0, typename F = void, typename... Args,
  153. typename Variant = std::invoke_result_t<F, Args...>,
  154. std::enable_if_t<support::is_template_instance_v<std::variant, std::remove_reference_t<Variant>>>* = nullptr
  155. >
  156. // TODO: [[nodiscard]] unless the required alternative is monostate
  157. decltype(auto) require(F&& f, Args&&... args)
  158. {
  159. // static_assert(first variant alternative does not repeat);
  160. return std::visit([fargs = std::forward_as_tuple(std::forward<F>(f), std::forward<Args>(args)...)](auto&& a)
  161. -> std::variant_alternative_t<index, Variant>
  162. {
  163. if constexpr (std::is_same_v<
  164. std::variant_alternative_t<index, Variant>,
  165. std::remove_reference_t<decltype(a)>>
  166. ) return std::forward<decltype(a)>(a);
  167. else throw std::apply([a](auto&&... eargs)
  168. // 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
  169. { return exception_wrapper(a, std::forward<decltype(eargs)>(eargs)...); },
  170. fargs);
  171. }, std::invoke(std::forward<F>(f), std::forward<Args>(args)...));
  172. };
  173. // TODO: request(F, Args...) noexcept - return first variant arg with index >0 or F(Args...)
  174. // 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
  175. int main() try
  176. {
  177. auto sf = require( open_f(meta::list(mode::write, mode::create), "helotherefile") );
  178. auto data = as_byte_view("uomo");
  179. // we offer up sf but it won't be taken unless error occurs in which case it'll be sent off with the exception
  180. // in general std move should really be called std::offer
  181. assert(( require( write_f, std::move(sf), data ) == data.end() ));
  182. assert(( require( write_f, std::move(sf), data ) == data.end() ));
  183. assert(( require( write_f, std::move(sf), data ) == data.end() ));
  184. std::array<std::byte, 10> buf{};
  185. std::byte* red = require( read_f, standard_input, as_byte_range(buf) );
  186. while(red)
  187. {
  188. assert(( require( write_f, standard_output, byte_view{buf.data(), red} ) == red ));
  189. red = require( read_f, standard_input, as_byte_range(buf));
  190. }
  191. const char* expected = "uomouomouomo";
  192. auto expected_data = as_byte_view(expected);
  193. auto rd = require( open_f, meta::list(mode::read), "helotherefile"s );
  194. assert(( support::mismatch(read_iterator(rd, as_byte_range(buf)), read_iterator(),
  195. expected_data.begin(), expected_data.end()) == std::pair{read_iterator(), expected_data.end()} ));
  196. assert( require( size_f, rd ) == 12 );
  197. require( resize_f, sf, 3 );
  198. assert( require( size_f, rd ) == 3 );
  199. require( seek_f, rd, 0 );
  200. auto resized = as_byte_view("uom");
  201. assert(( support::mismatch(read_iterator(rd, as_byte_range(buf)), read_iterator(),
  202. resized.begin(), resized.end()) == std::pair{read_iterator(), resized.end()} ));
  203. {
  204. auto expected_echo = as_byte_view("uomouomouomo\n");
  205. auto [process, out] = require( execute_f, meta::list{standard_output}, "echo"s, "echo"s, expected );
  206. assert(( support::mismatch(read_iterator(out, as_byte_range(buf)), read_iterator(),
  207. expected_echo.begin(), expected_echo.end()) == std::pair{read_iterator(), expected_echo.end()} ));
  208. }
  209. // TODO: remove them test files
  210. return 0;
  211. }
  212. catch(...)
  213. {
  214. perror("omaaaagooot");
  215. throw;
  216. }