A C++ toolbox repo until the pair uTL/dTL arives
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

315 line
10 KiB

  1. /*!
  2. * \file utils/print.h
  3. * \brief
  4. * A CRTP base class to provide print interface
  5. *
  6. * \copyright Copyright (C) 2021 Christos Choutouridis <christos@choutouridis.net>
  7. *
  8. * <dl class=\"section copyright\"><dt>License</dt><dd>
  9. * The MIT License (MIT)
  10. *
  11. * Permission is hereby granted, free of charge, to any person obtaining a copy
  12. * of this software and associated documentation files (the "Software"), to deal
  13. * in the Software without restriction, including without limitation the rights
  14. * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  15. * copies of the Software, and to permit persons to whom the Software is
  16. * furnished to do so, subject to the following conditions:
  17. *
  18. * The above copyright notice and this permission notice shall be included in all
  19. * copies or substantial portions of the Software.
  20. *
  21. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  22. * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  23. * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  24. * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  25. * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  26. * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  27. * SOFTWARE.
  28. * </dd></dl>
  29. */
  30. #ifndef TBX_UTILS_PRINT_H_
  31. #define TBX_UTILS_PRINT_H_
  32. #include <core/core.h>
  33. #include <core/crtp.h>
  34. #include <cstring>
  35. #include <math.h>
  36. #include <string_view>
  37. #include <type_traits>
  38. #include <utility>
  39. #include <limits>
  40. namespace tbx {
  41. /*!
  42. * \class Print
  43. * \brief
  44. * A CRTP print interface
  45. *
  46. * Requirements:
  47. * - size_t write_impl(const Char_t* buffer, size_t size) : Return the number of \c Char_t written
  48. * - size_t write_(const Char_t ch) : Return the number of \c Char_t written (normally one).
  49. *
  50. * \tparam Impl_t The derived type
  51. * \tparam Char_t The char type to use
  52. */
  53. template <typename Impl_t, typename Char_t>
  54. class Print {
  55. _CRTP_IMPL(Impl_t);
  56. public:
  57. using value_type = Char_t;
  58. using pointer_type = Char_t*;
  59. using iterator_type = Char_t*;
  60. using const_iterator_type = const Char_t*;
  61. using difference_type = std::ptrdiff_t;
  62. using size_type = size_t;
  63. using str_view_t = std::basic_string_view<Char_t>;
  64. //! Enumerator for number base formating
  65. enum class Base {
  66. BIN =2, OCT =8, DEC =10, HEX =16
  67. };
  68. private:
  69. //! \name CRTP requirements
  70. //! @{
  71. size_t write_(const Char_t* buffer, size_t size) {
  72. return impl().write_impl(buffer, size);
  73. }
  74. size_t write_(const Char_t ch) {
  75. return impl().write_impl(ch);
  76. }
  77. //! @}
  78. protected:
  79. Print() noexcept = default; //!< Construct from derived only
  80. private:
  81. //! Helper tool to convert strong enums to their underlying type
  82. template <typename E>
  83. constexpr typename std::underlying_type_t<E> value(E e) noexcept {
  84. return static_cast<typename std::underlying_type_t<E>>(e);
  85. }
  86. size_t print_unsigned(unsigned long n, Base base); // integer conversion base tool
  87. size_t print_double(double number, uint8_t digits); // double conversion base tool
  88. public:
  89. /*!
  90. * \brief
  91. * Prints a string view
  92. * \param str The string view to print
  93. * \return The number of printed \c Char_t
  94. */
  95. size_t print(const str_view_t str) {
  96. return write_(str.data(), str.size());
  97. }
  98. /*!
  99. * \brief
  100. * Prints a string
  101. * \param str Pointer to string to print
  102. * \return The number of printed \c Char_t
  103. */
  104. size_t print(const Char_t* str) {
  105. if (str == nullptr)
  106. return 0;
  107. return write_(str, std::strlen(str));
  108. }
  109. /*!
  110. * \brief
  111. * Prints a buffer of size \c size. If there is a null termination
  112. * before the end of the buffer, prints up to termination.
  113. *
  114. * \param str Pointer to string buffer to print
  115. * \param size The size of buffer
  116. * \return The number of printed \c Char_t
  117. */
  118. size_t print(const Char_t* str, size_t size) {
  119. if (str == nullptr)
  120. return 0;
  121. return write_(str, size);
  122. }
  123. /*!
  124. * \brief
  125. * Prints a \c Char_t
  126. * \param ch The Char_t to print
  127. * \return The number of printed \c Char_t
  128. */
  129. size_t print(Char_t ch) {
  130. return write_ (ch);
  131. }
  132. /*!
  133. * \brief
  134. * Convert and print a long.
  135. * \param n The number to print
  136. * \param base The number base to use for conversion.
  137. * \return The number of printed \c Char_t
  138. */
  139. size_t print(long n, Base base =Base::DEC) {
  140. size_t cnt =0;
  141. if (n < 0) {
  142. n = -n;
  143. cnt = write_ ('-');
  144. }
  145. return cnt + print_unsigned((unsigned long)n, base);
  146. }
  147. /*!
  148. * \brief
  149. * Convert and print an int.
  150. * \param n The number to print
  151. * \param base The number base to use for conversion.
  152. * \return The number of printed \c Char_t
  153. */
  154. size_t print(int n, Base base= Base::DEC) {
  155. return print ((long)n, base);
  156. }
  157. /*!
  158. * \brief
  159. * Convert and print an unsigned long.
  160. * \param n The number to print
  161. * \param base The number base to use for conversion.
  162. * \return The number of printed \c Char_t
  163. */
  164. size_t print(unsigned long n, Base base= Base::DEC) {
  165. return print_unsigned ((unsigned long)n, base);
  166. }
  167. /*!
  168. * \brief
  169. * Convert and print an unsigned int.
  170. * \param n The number to print
  171. * \param base The number base to use for conversion.
  172. * \return The number of printed \c Char_t
  173. */
  174. size_t print(unsigned int n, Base base= Base::DEC) {
  175. return print_unsigned ((unsigned long)n, base);
  176. }
  177. /*!
  178. * \brief
  179. * Convert and print adouble
  180. * \param n The number to print
  181. * \param digits The number of decimal digits to print
  182. * \return The number of printed \c Char_t
  183. */
  184. size_t print(double n, uint8_t digits = 2) {
  185. return print_double (n, digits);
  186. }
  187. /*!
  188. * \brief
  189. * Perfect forwarder to print functionality with a new line termination
  190. * \tparam Ts The types of parameters
  191. * \param args The arguments to pass
  192. * \return The number of printed \c Char_t
  193. */
  194. template <typename ...Ts>
  195. size_t println(Ts&& ...args) {
  196. size_t r = print (std::forward<Ts>(args)...);
  197. r += write_ ('\n');
  198. return r;
  199. }
  200. };
  201. /*!
  202. * \brief
  203. * Converts and prints an unsigned long
  204. *
  205. * \tparam Impl_t The derived type
  206. * \tparam Char_t The char type to use
  207. *
  208. * \param n The number to print
  209. * \param base The number base to use
  210. * \return The number of printed \c Char_t
  211. */
  212. template <typename Impl_t, typename Char_t>
  213. size_t Print<Impl_t, Char_t>::print_unsigned(unsigned long n, Base base) {
  214. Char_t buf[8 *sizeof(Char_t) * sizeof(long) + 1]; // Assumes 8-bit chars plus zero byte.
  215. Char_t *str = &buf[sizeof(buf) - 1];
  216. *str = '\0';
  217. do {
  218. Char_t c = n % value(base);
  219. n /= value(base);
  220. *--str = c < 10 ? c + '0' : c + 'A' - 10;
  221. } while(n);
  222. return write_(str, std::strlen(str));
  223. }
  224. /*!
  225. * \brief
  226. * Converts and prints a double
  227. *
  228. * \note
  229. * Internally, this implementation uses a long to store the integer part of the number.
  230. * Thus overflows for numbers bigger than std::numeric_limits<long>::max() / min().
  231. * For these numbers it prints "ovf" instead.
  232. *
  233. * \tparam Impl_t The derived type
  234. * \tparam Char_t The char type to use
  235. *
  236. * \param n The number to print
  237. * \param digits The number of decimal digits to print
  238. * \return The number of printed \c Char_t
  239. */
  240. template <typename Impl_t, typename Char_t>
  241. size_t Print<Impl_t, Char_t>::print_double(double number, uint8_t digits) {
  242. size_t n = 0;
  243. if (std::isnan(number)) return print("nan");
  244. if (std::isinf(number)) return print("inf");
  245. if (number > (double)std::numeric_limits<long>::max())
  246. return print ("ovf");
  247. if (number < (double)std::numeric_limits<long>::min())
  248. return print ("-ovf");
  249. // Handle negative numbers
  250. if (number < 0.0) {
  251. n += write_ ('-');
  252. number = -number;
  253. }
  254. // Round correctly so that print(1.999, 2) prints as "2.00"
  255. double rounding = 0.5;
  256. for (uint8_t i=0; i<digits; ++i)
  257. rounding /= 10.0;
  258. number += rounding;
  259. // Extract the integer part of the number and print it
  260. unsigned long int_part = (unsigned long)number;
  261. double remainder = number - (double)int_part;
  262. n += print(int_part);
  263. // Print the decimal point, but only if there are digits beyond
  264. if (digits > 0) {
  265. n += print('.');
  266. }
  267. // Extract digits from the remainder one at a time
  268. while (digits-- > 0) {
  269. remainder *= 10.0;
  270. unsigned int toPrint = (unsigned int)(remainder);
  271. n += print(toPrint);
  272. remainder -= toPrint;
  273. }
  274. return n;
  275. }
  276. }
  277. #endif /* TBX_UTILS_PRINT_H_ */