From 2610bd0cb7c83c6b3c7a215a85ee5ed0fa7222fa Mon Sep 17 00:00:00 2001 From: Viatorus Date: Wed, 8 Nov 2023 07:16:14 +0000 Subject: [PATCH] =?UTF-8?q?Deploying=20to=20gh-pages=20from=20@=20Viatorus?= =?UTF-8?q?/emio@0da267ad38581842137b753a0a0a5de6c124437d=20=F0=9F=9A=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- dist/0.6.1/emio.hpp | 7158 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 7158 insertions(+) create mode 100644 dist/0.6.1/emio.hpp diff --git a/dist/0.6.1/emio.hpp b/dist/0.6.1/emio.hpp new file mode 100644 index 0000000..552d30e --- /dev/null +++ b/dist/0.6.1/emio.hpp @@ -0,0 +1,7158 @@ +/* + em{io} is a safe and fast high-level and low-level character input/output + library for bare-metal and RTOS based embedded systems with a very small + binary footprint. + + Copyright (c) 2021 - present, Toni Neubert (viatorus/emio) + Copyright (c) 2012 - present, Victor Zverovich (fmtlib/fmt) + + Permission is hereby granted, free of charge, to any person obtaining + a copy of this software and associated documentation files (the + "Software"), to deal in the Software without restriction, including + without limitation the rights to use, copy, modify, merge, publish, + distribute, sublicense, and/or sell copies of the Software, and to + permit persons to whom the Software is furnished to do so, subject to + the following conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE + LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION + OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION + WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + + --- Optional exception to the license --- + + As an exception, if, as a result of your compiling your source code, portions + of this Software are embedded into a machine-executable object form of such + source code, you may redistribute such embedded portions in such object form + without including the above copyright and permission notices. + */ + +#ifndef EMIO_Z_MAIN_H +#define EMIO_Z_MAIN_H + +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include + +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include +#include +#include +#include + +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include +#include +#include +#include +#include +#include +#include + +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +namespace emio::detail { + +// Helper macros for removing parentheses. +#define EMIO_Z_INTERNAL_DEPAREN(X) EMIO_Z_INTERNAL_ESC(EMIO_Z_INTERNAL_ISH_EMIO_INTERNAL X) +#define EMIO_Z_INTERNAL_ISH_EMIO_INTERNAL(...) EMIO_Z_INTERNAL_ISH_EMIO_INTERNAL __VA_ARGS__ +#define EMIO_Z_INTERNAL_ESC(...) EMIO_Z_INTERNAL_ESC_(__VA_ARGS__) +#define EMIO_Z_INTERNAL_ESC_(...) EMIO_Z_INTERNAL_VAN##__VA_ARGS__ +#define EMIO_Z_INTERNAL_VANEMIO_Z_INTERNAL_ISH_EMIO_INTERNAL + +// Helper macros for generating an unique name. +#define EMIO_Z_INTERNAL_GLUE2(x, y) x##y +#define EMIO_Z_INTERNAL_GLUE(x, y) EMIO_Z_INTERNAL_GLUE2(x, y) +#define EMIO_Z_INTERNAL_UNIQUE_NAME EMIO_Z_INTERNAL_GLUE(_emio_try_unique_name_temporary, __COUNTER__) + +#define EMIO_Z_INTERNAL_TRYV(name, expr) \ + do { \ + if (auto name = (expr); name.has_error()) [[unlikely]] { \ + return name.assume_error(); \ + } \ + } while (0) + +#define EMIO_Z_INTERNAL_TRY(name, var, expr) \ + auto name = (expr); \ + if (name.has_error()) [[unlikely]] { \ + return name.assume_error(); \ + } \ + EMIO_Z_INTERNAL_DEPAREN(var) = std::move(name).assume_value() + +#if defined(__GNUC__) || defined(__GNUG__) +// Separate macro instead of std::is_constant_evaluated() because code will be optimized away even in debug if inlined. +# define EMIO_Z_INTERNAL_IS_CONST_EVAL __builtin_is_constant_evaluated() +# define EMIO_Z_INTERNAL_UNREACHABLE __builtin_unreachable() +#else +# define EMIO_Z_INTERNAL_IS_CONST_EVAL std::is_constant_evaluated() +# define EMIO_Z_INTERNAL_UNREACHABLE std::terminate() +#endif + +#if defined(EMIO_ENABLE_DEV_ASSERT) +# define EMIO_Z_DEV_ASSERT(...) \ + do { \ + if (!(__VA_ARGS__)) { \ + std::terminate(); \ + } \ + } while (0) +#else +# define EMIO_Z_DEV_ASSERT(...) static_cast(__VA_ARGS__) +#endif + +} // namespace emio::detail + +namespace emio::detail { + +constexpr bool isalpha(char c) { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); +} + +constexpr bool isdigit(char c) { + return (c >= '0' && c <= '9'); +} + +constexpr bool is_valid_number_base(const int base) noexcept { + return base >= 2 && base <= 36; +} + +constexpr std::optional char_to_digit(const char c, const int base) noexcept { + if (c < '0') { + return std::nullopt; + } + int res{}; + if (c >= 'a') { + res = c - 'a' + 10; + } else if (c >= 'A') { + res = c - 'A' + 10; + } else { + res = c - '0'; + } + if (res < base) { + return res; + } + return std::nullopt; +} + +constexpr char digit_to_char(const int digit, bool upper) noexcept { + if (digit >= 10) { + EMIO_Z_DEV_ASSERT(digit < 36); + if (upper) { + return static_cast(static_cast('A') + (digit - 10)); + } + return static_cast(static_cast('a') + (digit - 10)); + } + EMIO_Z_DEV_ASSERT(digit < 10); + return static_cast(static_cast('0') + digit); +} + +template + requires(std::is_unsigned_v) +constexpr size_t count_digits_10(T number) noexcept { + size_t count = 1; + for (;;) { + // Integer division is slow so do it for a group of four digits instead + // of for every digit. The idea comes from the talk by Alexandrescu + // "Three Optimization Tips for C++". + if (number < 10) { + return count; + } + if (number < 100) { + return count + 1; + } + if (number < 1000) { + return count + 2; + } + if (number < 10000) { + return count + 3; + } + number /= 10000U; + count += 4; + } +} + +template + requires(std::is_unsigned_v) +constexpr size_t count_digits(T number) noexcept { + if (number == 0) { + return 1; + } + + if constexpr (Base == 10) { + return count_digits_10(number); + } else if constexpr (Base == 2) { + return static_cast(std::bit_width(number)); + } else if constexpr (Base == 8) { + return static_cast((std::bit_width(number) + 2) / 3); + } else if constexpr (Base == 16) { + return static_cast(((std::bit_width(number) + 3) / 4)); + } else { + size_t digit_cnt{1}; + for (number /= static_cast(Base); number; number /= static_cast(Base)) { + ++digit_cnt; + } + return digit_cnt; + } +} + +template + requires(std::is_unsigned_v) +constexpr size_t get_number_of_digits(T number, int base) noexcept { + if (number == 0) { + return 1; + } + if (base == 10) { + return count_digits<10>(number); + } + if (base == 16) { + return count_digits<16>(number); + } + if (base == 2) { + return count_digits<2>(number); + } + if (base == 8) { + return count_digits<8>(number); + } + size_t digit_cnt{1}; + for (number /= static_cast(base); number; number /= static_cast(base)) { + ++digit_cnt; + } + return digit_cnt; +} + +template +constexpr bool is_negative(T value) noexcept { + if constexpr (std::is_signed_v) { + return value < 0; + } else { + return false; + } +} + +template +constexpr int num_bits() noexcept { + return std::numeric_limits::digits; +} + +template +using int32_or_64 = std::conditional_t() <= 32, int32_t, int64_t>; + +template +using uint32_or_64 = std::conditional_t() <= 32, uint32_t, uint64_t>; + +template +using upcasted_int_t = std::conditional_t, int32_or_64, uint32_or_64>; + +template + requires(std::is_integral_v) +constexpr auto integer_upcast(T integer) noexcept { + return static_cast>(integer); +} + +template +constexpr uint32_or_64 to_absolute(T number) noexcept { + if constexpr (std::is_unsigned_v) { + return number; + } else { + if (is_negative(number)) { + auto abs = static_cast>(number); + abs = T{} - abs; + return abs; + } + return static_cast>(number); + } +} + +template +constexpr std::make_unsigned_t to_unsigned(T number) noexcept { + return static_cast>(number); +} + +template +constexpr std::make_signed_t to_signed(T number) noexcept { + return static_cast>(number); +} + +// Converts value in the range [0, 100) to a string. +inline constexpr const char* digits2(size_t value) noexcept { + // GCC generates slightly better code when value is pointer-size. + return &"0001020304050607080910111213141516171819" + "2021222324252627282930313233343536373839" + "4041424344454647484950515253545556575859" + "6061626364656667686970717273747576777879" + "8081828384858687888990919293949596979899"[value * 2]; +} + +// Copies two characters from src to dst. +template +inline constexpr void copy2(Char* dst, const char* src) noexcept { + if (EMIO_Z_INTERNAL_IS_CONST_EVAL) { + *dst++ = static_cast(*src++); + *dst = static_cast(*src); + } else { + memcpy(dst, src, 2); + } +} + +template + requires(std::is_unsigned_v) +constexpr char* write_decimal(T abs_number, char* next) noexcept { + // Write number from right to left. + while (abs_number >= 100) { + next -= 2; + copy2(next, digits2(static_cast(abs_number % 100))); + abs_number /= 100; + } + if (abs_number < 10) { + *--next = '0' + static_cast(abs_number); + return next; + } + next -= 2; + copy2(next, digits2(static_cast(abs_number))); + return next; +} + +template + requires(std::is_unsigned_v) +constexpr char* write_uint(T abs_number, const bool upper, char* next) noexcept { + const char* digits = upper ? "0123456789ABCDEF" : "0123456789abcdef"; + do { + T digit = static_cast(abs_number & ((1 << BaseBits) - 1)); + if constexpr (BaseBits < 4) { + EMIO_Z_DEV_ASSERT(digit < 8); + *--next = static_cast('0' + digit); + } else { + EMIO_Z_DEV_ASSERT(digit < 16); + *--next = digits[digit]; + } + } while ((abs_number >>= BaseBits) != 0); + return next; +} + +template + requires(std::is_unsigned_v) +constexpr char* write_number(T abs_number, const int base, const bool upper, char* next) noexcept { + if (base == 10) { + return write_decimal(abs_number, next); + } + if (base == 16) { + return write_uint<4>(abs_number, upper, next); + } + if (base == 2) { + return write_uint<1>(abs_number, false, next); + } + if (base == 8) { + return write_uint<3>(abs_number, false, next); + } + if (abs_number == 0) { + *(--next) = '0'; + return next; + } + // Write number from right to left. + for (; abs_number; abs_number /= static_cast(base)) { + const char c = digit_to_char(static_cast(abs_number % static_cast(base)), upper); + *(--next) = c; + } + return next; +} + +inline constexpr size_t npos = std::string_view::npos; + +constexpr std::string_view unchecked_substr(const std::string_view& str, size_t pos, size_t n = npos) noexcept { + const size_t rlen = std::min(n, str.length() - pos); + return {str.data() + pos, rlen}; +} + +template +constexpr char* fill_n(char* out, Size count, char value) noexcept { + if (EMIO_Z_INTERNAL_IS_CONST_EVAL) { + for (Size i = 0; i < count; i++) { + *out++ = value; + } + return out; + } else { + std::memset(out, value, to_unsigned(count)); + return out + count; + } +} + +template +constexpr char* copy_n(const char* in, Size count, char* out) noexcept { + if (EMIO_Z_INTERNAL_IS_CONST_EVAL) { + for (Size i = 0; i < count; i++) { + *out++ = *in++; + } + return out; + } else { + std::memcpy(out, in, to_unsigned(count)); + return out + count; + } +} + +[[nodiscard]] inline constexpr bool equal_n(const char* a, const char* b, const size_t n) { + if (EMIO_Z_INTERNAL_IS_CONST_EVAL) { + for (size_t i = 0; i < n; i++) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } else { + return std::memcmp(a, b, n) == 0; + } +} + +using namespace std::string_view_literals; + +// Helper function to construct string literals directly as string_view during compilation if string_view_literal +// operator "" sv is not available. +inline consteval std::string_view sv(std::string_view sv) noexcept { + return sv; +} + +} // namespace emio::detail + +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include +#include +#include +#include +#include + +/** + * Evaluates an expression. If successful, continues the execution. If unsuccessful, immediately returns from the + * calling function. + * @param expr The expression to evaluate. + */ +#define EMIO_TRYV(expr) EMIO_Z_INTERNAL_TRYV(EMIO_Z_INTERNAL_UNIQUE_NAME, expr) + +/** + * Evaluates an expression. If successful, assigns the value to a declaration. If unsuccessful, immediately returns from + * the calling function. + * @param var The declaration to assign the value to. + * @param expr The expression to evaluate. + */ +#define EMIO_TRY(var, expr) EMIO_Z_INTERNAL_TRY(EMIO_Z_INTERNAL_UNIQUE_NAME, var, expr) + +namespace emio { + +/** + * A list of possible I/O errors. + */ +enum class err { + // success = 0, ///< Internal used for no error. + eof = 1, ///< End of file (e.g. reaching the end of an output array). + invalid_argument, ///< A parameter is incorrect (e.g. the output base is invalid). + invalid_data, ///< The data is malformed (e.g. no digit where a digit was expected). + out_of_range, ///< The parsed value is not in the range representable by the type (e.g. parsing 578 as uint8_t). + invalid_format, ///< The format string is invalid. +}; + +/** + * Returns the name of the possible I/O errors. + * @param error The error. + * @return The name. + */ +constexpr std::string_view to_string(err error) noexcept { + using namespace std::string_view_literals; + + if (error == err{}) { + return "no error"sv; + } + switch (error) { + case err::eof: + return "eof"sv; + case err::invalid_argument: + return "invalid argument"sv; + case err::invalid_data: + return "invalid data"sv; + case err::out_of_range: + return "out of range"sv; + case err::invalid_format: + return "invalid format"sv; + } + std::terminate(); +} + +/** + * Used to indicates a successful operation without any value. + */ +inline constexpr struct { +} success; + +/** + * This exception type indicating an incorrect observation of value or error occurred by result. + * + * No member functions are added in addition to std::logic_error. Typical .what() strings are: + * - "no value" -> a value should be accessed but result held an error + * - "no error" -> a error should be accessed but result held an value + */ +class bad_result_access : public std::logic_error { + public: + /** + * Constructs the bad result access from a message. + * @param msg The exception message. + */ + explicit bad_result_access(const std::string_view& msg) : logic_error{msg.data()} {} +}; + +namespace detail { + +#ifdef __EXCEPTIONS +inline constexpr bool exceptions_disabled = false; +#else +inline constexpr bool exceptions_disabled = true; +#endif + +// Helper function to throw or terminate, depending on whether exceptions are globally enabled or not. +[[noreturn]] inline void throw_bad_result_access_or_terminate(const err error) noexcept(exceptions_disabled) { +#ifdef __EXCEPTIONS + throw bad_result_access{to_string(error)}; +#else + static_cast(error); + std::terminate(); +#endif +} + +} // namespace detail + +/** + * This class provides a way to store an optional value after an successful operation or an error after an failed + * operation. The error type is emio::err. + * @note See boost::outcome, std::expected or rust's std::result for the basic idea. + * This provided API is a mix of each world. + * @tparam T The value type to hold on success. + */ +template +class [[nodiscard]] result; + +/** + * Partial template specification of result for any non-reference type. + * @tparam Value The value type to hold on success. + */ +template + requires(!std::is_reference_v) +class [[nodiscard]] result { + public: + /** + * Explicit not default constructable. + */ + constexpr result() = delete; + + /** + * Constructs a result from a value to indicate an success. + * @param value The value. + */ + template + requires(std::is_constructible_v) + // NOLINTNEXTLINE(bugprone-forwarding-reference-overload): Is guarded by require clause. + constexpr explicit(!std::is_convertible_v) result(U&& value) : value_{std::forward(value)} {} + + /** + * Constructs a result from an error to indicate a failure. + * @param error The error. + */ + constexpr result(err error) : error_{error} { + EMIO_Z_DEV_ASSERT(error != err{}); + } + + /** + * Checks whether the object holds a value. + * @return True if it holds a value, otherwise false. + */ + constexpr explicit operator bool() const noexcept { + return has_value(); + } + + /** + * Checks whether the object holds a value. + * @return True if it holds a value, otherwise false. + */ + [[nodiscard]] constexpr bool has_value() const noexcept { + return value_.has_value(); + } + + /** + * Checks whether the object holds an error. + * @return True if it holds an error, otherwise false. + */ + [[nodiscard]] constexpr bool has_error() const noexcept { + return !has_value(); + } + + /** + * Returns a pointer to the value. + * @note The behavior is undefined if this->has_value() is false. + * @return The value. + */ + constexpr std::remove_reference_t* operator->() noexcept { + EMIO_Z_DEV_ASSERT(has_value()); + return &*value_; + } + + /** + * Returns a const pointer to the value. + * @note The behavior is undefined if this->has_value() is false. + * @return The value. + */ + constexpr const std::remove_reference_t* operator->() const noexcept { + EMIO_Z_DEV_ASSERT(has_value()); + return &*value_; + } + + /** + * Returns a reference to the value. + * @note The behavior is undefined if this->has_value() is false. + * @return The value. + */ + [[nodiscard]] constexpr Value& operator*() & noexcept { + EMIO_Z_DEV_ASSERT(has_value()); + return *value_; + } + + /** + * Returns a const reference to the value. + * @note The behavior is undefined if this->has_value() is false. + * @return The value. + */ + [[nodiscard]] constexpr const Value& operator*() const& noexcept { + EMIO_Z_DEV_ASSERT(has_value()); + return *value_; + } + + /** + * Returns a rvalue reference to the value. + * @note The behavior is undefined if this->has_value() is false. + * @return The value. + */ + [[nodiscard]] constexpr Value&& operator*() && noexcept { + EMIO_Z_DEV_ASSERT(has_value()); + return std::move(*value_); + } + + /** + * Returns a const rvalue reference to the value. + * @note The behavior is undefined if this->has_value() is false. + * @return The value. + */ + [[nodiscard]] constexpr const Value&& operator*() const&& noexcept { + EMIO_Z_DEV_ASSERT(has_value()); + return std::move(*value_); + } + + /** + * Returns a reference to the value. + * @note The behavior is undefined if this->has_value() is false. + * @return The value. + */ + [[nodiscard]] constexpr Value& assume_value() & noexcept { + EMIO_Z_DEV_ASSERT(has_value()); + return *value_; // NOLINT(bugprone-unchecked-optional-access): assumed + } + + /** + * Returns a const reference to the value. + * @note The behavior is undefined if this->has_value() is false. + * @return The value. + */ + [[nodiscard]] constexpr const Value& assume_value() const& noexcept { + EMIO_Z_DEV_ASSERT(has_value()); + return *value_; // NOLINT(bugprone-unchecked-optional-access): assumed + } + + /** + * Returns a rvalue reference to the value. + * @note The behavior is undefined if this->has_value() is false. + * @return The value. + */ + [[nodiscard]] constexpr Value&& assume_value() && noexcept { + EMIO_Z_DEV_ASSERT(has_value()); + return std::move(*value_); // NOLINT(bugprone-unchecked-optional-access): assumed + } + + /** + * Returns a const rvalue reference to the value. + * @note The behavior is undefined if this->has_value() is false. + * @return The value. + */ + [[nodiscard]] constexpr const Value&& assume_value() const&& noexcept { + EMIO_Z_DEV_ASSERT(has_value()); + return std::move(*value_); // NOLINT(bugprone-unchecked-optional-access): assumed + } + + /** + * Returns a reference to the value. + * @return The value. + */ + constexpr Value& value() & noexcept(detail::exceptions_disabled) { + if (value_.has_value()) [[likely]] { + return *value_; + } + detail::throw_bad_result_access_or_terminate(error_); + } + + /** + * Returns a const reference to the value. + * @return The value. + */ + [[nodiscard]] constexpr const Value& value() const& noexcept(detail::exceptions_disabled) { + if (value_.has_value()) [[likely]] { + return *value_; + } + detail::throw_bad_result_access_or_terminate(error_); + } + + /** + * Returns a rvalue reference to the value. + * @return The value. + */ + constexpr Value&& value() && noexcept(detail::exceptions_disabled) { + if (value_.has_value()) [[likely]] { + return std::move(*value_); + } + detail::throw_bad_result_access_or_terminate(error_); + } + + /** + * Returns a const rvalue reference to the value. + * @return The value. + */ + // NOLINTNEXTLINE(modernize-use-nodiscard): access check + constexpr const Value&& value() const&& noexcept(detail::exceptions_disabled) { + if (value_.has_value()) [[likely]] { + return std::move(*value_); + } + detail::throw_bad_result_access_or_terminate(error_); + } + + /** + * Returns the error. + * @note The behavior is undefined if this->has_error() is false. + * @return The error. + */ + [[nodiscard]] constexpr err assume_error() const noexcept { + EMIO_Z_DEV_ASSERT(!has_value()); + return error_; + } + + /** + * Returns the error. + * @return The error. + * @throws bad_result_access exception or terminates (if exceptions are disabled) this->has_error() is false. + */ + constexpr err error() const noexcept(detail::exceptions_disabled) { // NOLINT(modernize-use-nodiscard): access check + if (has_error()) [[likely]] { + return error_; + } + detail::throw_bad_result_access_or_terminate(error_); + return {}; // afl-c++ requires a return statement. + } + + /** + * Returns the contained value if *this has a value, otherwise returns default_value. + * @param default_value The value to use in case *this is empty. + * @return The current value if *this has a value, or default_value otherwise. + */ + template + constexpr Value value_or(U&& default_value) const& { + return value_.value_or(std::forward(default_value)); + } + + /** + * Returns the contained value if *this has a value, otherwise returns default_value. + * @param default_value The value to use in case *this is empty. + * @return The current value if *this has a value, or default_value otherwise. + */ + template + constexpr Value value_or(U&& default_value) && { + return value_.value_or(std::forward(default_value)); + } + + private: + std::optional value_{}; + err error_{}; +}; + +/** + * Partial template specification of result for no type (void). + * @note Although result does not hold any value, the functions like has_value, assume_value and value are + * provided with the exact same name to keep the API uniform. + */ +template <> +class [[nodiscard]] result { + public: + /** + * Explicit not default constructable. + */ + constexpr result() = delete; + + /** + * Constructs a result from the success value to indicate an success. + * @param s The success value. + */ + constexpr result([[maybe_unused]] decltype(success) s) {} + + /** + * Constructs a result from an error to indicate a failure. + * @param error The error. + */ + constexpr result(err error) : error_{error} { + EMIO_Z_DEV_ASSERT(error != err{}); + } + + /** + * Constructs a result from another non void result and copies the success or error state. + * @param other The other result. + */ + template + requires(!std::is_void_v) + constexpr result(const result& other) { + if (other.has_error()) { + error_ = other.assume_error(); + } + } + + /** + * Checks whether the object holds a value. + * @return True if it holds a value, otherwise false. + */ + constexpr explicit operator bool() const noexcept { + return has_value(); + } + + /** + * Checks whether the object holds a value. + * @return True if it holds a value, otherwise false. + */ + [[nodiscard]] constexpr bool has_value() const noexcept { + return error_ == err{}; + } + + /** + * Checks whether the object holds an error. + * @return True if it holds an error, otherwise false. + */ + [[nodiscard]] constexpr bool has_error() const noexcept { + return !has_value(); + } + + /** + * Does nothing for this type. Kept for consistent API. + */ + // NOLINTNEXTLINE(readability-convert-member-functions-to-static): Kept non-static for consistent API. + constexpr void assume_value() const noexcept { + // Nothing. + EMIO_Z_DEV_ASSERT(has_value()); + } + + /** + * If the result does have an error, this function throws/terminates. + * @throws bad_result_access exception or terminates (if exceptions are disabled) if this->has_value() is false. + */ + constexpr void value() const noexcept(detail::exceptions_disabled) { + if (!has_value()) [[unlikely]] { + detail::throw_bad_result_access_or_terminate(error_); + } + } + + /** + * Returns the error. + * @note The behavior is undefined if this->has_error() is false. + * @return The error. + */ + [[nodiscard]] constexpr err assume_error() const noexcept { + EMIO_Z_DEV_ASSERT(!has_value()); + return error_; + } + + /** + * Returns the error. + * @return The error. + * @throws bad_result_access exception or terminates (if exceptions are disabled) this->has_error() is false. + */ + constexpr err error() const noexcept(detail::exceptions_disabled) { // NOLINT(modernize-use-nodiscard): access check + if (has_error()) [[likely]] { + return error_; + } + detail::throw_bad_result_access_or_terminate(error_); + return {}; // afl-c++ requires a return statement. + } + + private: + err error_{}; +}; + +namespace detail { + +template +struct is_result : std::false_type {}; + +template +struct is_result> : std::true_type {}; + +template +inline constexpr bool is_result_v = is_result::value; + +} // namespace detail + +/** + * Compares two result objects of maybe different but equality comparable types against each other. + * @param left The left one. + * @param right The right one. + * @details Comparison against inequality will be automatically generated. + * @return True if equal, otherwise false. + * They are equal if: + * - left.has_value() && right.has_value() && left.value() == right.value() + * - left.has_error() && right.has_error() && left.error() == right.error() + */ +template + requires((std::is_void_v && std::is_void_v) || std::equality_comparable_with) +constexpr bool operator==(const result& left, const result& right) noexcept { + if (left.has_value() != right.has_value()) { + return false; + } + if (left.has_value()) { + if constexpr (std::is_void_v && std::is_void_v) { + return true; + } else { + return left.assume_value() == right.assume_value(); + } + } + return left.assume_error() == right.assume_error(); +} + +/** + * Compares a result object with an object of maybe different but equality comparable type against each other. + * @param left The left one. + * @param right The right one. + * @details Comparison against inequality or right ==/!= left will be automatically generated. + * @return True if equal, otherwise false. + * They are equal if: + * - left.has_value() && left.value() == right + */ +template + requires(!detail::is_result_v && std::equality_comparable_with) +constexpr bool operator==(const result& left, const U& right) noexcept { + if (left.has_value()) { + return left.value() == right; + } + return false; +} + +/** + * Compares a result object against an error value for equality. + * @param left The left one. + * @param right The right one. + * @details Comparison against inequality or right ==/!= left will be automatically generated. + * @return True if equal, otherwise false. + * They are equal if: + * - left.has_error() && left.error() == right + */ +template +constexpr bool operator==(const result& left, const err right) noexcept { + return left.has_error() && left.error() == right; +} + +} // namespace emio + +namespace emio { + +// Forward declaration. +class reader; + +namespace detail { + +inline constexpr const char*& get_it(reader& rdr) noexcept; + +inline constexpr const char* get_end(reader& rdr) noexcept; + +inline constexpr result parse_sign(reader& in) noexcept; + +template +constexpr result parse_int(reader& in, int base, bool is_negative) noexcept; + +} // namespace detail + +/** + * This class operates on a char sequence and allows reading and parsing from it. + * The reader interprets the char sequence as finite input stream. After every successful operation the read position + * moves on until the last char of the sequence has been consumed. + */ +class reader { + public: + /// The size type. + using size_type = typename std::string_view::size_type; + /// Special value to indicate the end of the view or an error by functions returning an index. + static constexpr size_type npos = std::string_view::npos; + + /** + * Constructs an empty reader. + */ + constexpr reader() = default; + + // Don't allow temporary strings or any nullptr. + constexpr reader(std::string&&) = delete; // NOLINT(cppcoreguidelines-rvalue-reference-param-not-moved): as intended + constexpr reader(std::nullptr_t) = delete; + constexpr reader(int) = delete; + + /** + * Constructs the reader from any suitable char sequence. + * @param input The char sequence. + */ + template + requires(std::is_constructible_v && !std::is_same_v) + // NOLINTNEXTLINE(bugprone-forwarding-reference-overload): Is guarded by require clause. + constexpr explicit reader(Arg&& input) noexcept : reader{std::string_view{std::forward(input)}} {} + + /** + * Constructs the reader from a string view. + * @param sv The string view. + */ + constexpr explicit reader(const std::string_view& sv) noexcept : begin_{sv.begin()}, it_{begin_}, end_{sv.end()} {} + + /** + * Returns the current read position. + * @return The read position. + */ + [[nodiscard]] constexpr size_t pos() const noexcept { + return static_cast(it_ - begin_); + } + + /** + * Checks if the end of the stream has been reached. + * @return True if reached and all chars are read, otherwise false. + */ + [[nodiscard]] constexpr bool eof() const noexcept { + return end_ == it_; + } + + /** + * Returns the number of the not yet read chars of the stream. + * @return The number of remaining chars. + */ + [[nodiscard]] constexpr size_t cnt_remaining() const noexcept { + return static_cast(end_ - it_); + } + + /** + * Obtains a view of the not yet read chars of the stream. + * @return The view over the remaining chars. + */ + [[nodiscard]] constexpr std::string_view view_remaining() const noexcept { + return {it_, end_}; + } + + /** + * Reads all remaining chars from the stream. + * @return The remaining chars. Could be empty. + */ + [[nodiscard]] constexpr std::string_view read_remaining() noexcept { + const std::string_view remaining_view = view_remaining(); + it_ = end_; + return remaining_view; + } + + /** + * Pops one (default) or n chars from the reader. + * @note Does never overflow. + * @param cnt The number of chars to pop. + */ + constexpr void pop(const size_t cnt = 1) noexcept { + if (static_cast(end_ - it_) >= cnt) { + it_ += cnt; + } else { + it_ = end_; + } + } + + /** + * Makes one (default) or n chars available again to read. + * @note Does never underflow. + * @param cnt The number of chars to unpop. + */ + constexpr void unpop(const size_t cnt = 1) noexcept { + if (static_cast(it_ - begin_) >= cnt) { + it_ -= cnt; + } else { + it_ = begin_; + } + } + + /** + * Returns a newly constructed reader over the not yet read char sequence of the range [pos, pos + len). + * If len is greater than the size of the remaining chars, the end of the char sequence is used. + * @param pos The position of the first char to include. + * @param len The length of the char sequence. + * @return EOF if the position is outside the char sequence. + */ + constexpr result subreader(const size_t pos, const size_t len = npos) const noexcept { + const char* const next_it = it_ + pos; + if (next_it > end_) { + return err::eof; + } + const size_t rlen = std::min(len, static_cast(end_ - next_it)); + return reader{std::string_view{next_it, rlen}}; + } + + /** + * Returns the next char from the stream without consuming it. + * @return EOF if the end of the stream has been reached. + */ + constexpr result peek() const noexcept { + if (it_ != end_) { + return *it_; + } + return err::eof; + } + + /** + * Reads one char from the stream. + * @return EOF if the end of the stream has been reached. + */ + constexpr result read_char() noexcept { + if (it_ != end_) { + return *it_++; + } + return err::eof; + } + + /** + * Reads n chars from the stream. + * @param n The number of chars to read. + * @return EOF if the end of the stream has been reached before reading n chars. + */ + constexpr result read_n_chars(const size_t n) noexcept { + if (static_cast(end_ - it_) >= n) { + std::string_view res{it_, it_ + n}; + it_ += n; + return res; + } + return err::eof; + } + + /** + * Parses an integer from the stream. + * @tparam T The type of the integer. + * @param base The input base of the integer. Must be greater equal 2 and less equal 36. + * @return invalid_argument if the requested input base is not supported, EOF if the end of the stream has been + * reached or invalid_data if the char sequence cannot be parsed as integer. + */ + template + requires(std::is_integral_v) + constexpr result parse_int(const int base = 10) noexcept { + // Store current read position. + const char* const backup_it = it_; + + // Reduce code generation by upcasting the integer. + using upcasted_t = detail::upcasted_int_t; + const result res = parse_sign_and_int(base); + if (!res) { + it_ = backup_it; + return res.assume_error(); + } + if constexpr (std::is_same_v) { + return res; + } else { + // Check if upcast int is within the integer type range. + const upcasted_t val = res.assume_value(); + if (val < std::numeric_limits::min() || val > std::numeric_limits::max()) { + it_ = backup_it; + return err::out_of_range; + } + return static_cast(val); + } + } + + /** + * Parse options for read_until operations. + */ + struct read_until_options { + bool include_delimiter{false}; ///< If true, the delimiter is part of the output char sequence, otherwise not. + bool keep_delimiter{false}; ///< If true, the delimiter will be kept inside the stream, otherwise consumed. + bool ignore_eof{false}; ///< If true, reaching EOF does result in a failed read, otherwise in a successfully read. + }; + + /** + * Reads multiple chars from the stream until a given char as delimiter is reached or EOF (configurable). + * @param delimiter The char delimiter. + * @param options The read until options. + * @return invalid_data if the delimiter hasn't been found and ignore_eof is set to true or EOF if the stream is + * empty. + */ + constexpr result read_until_char( + const char delimiter, const read_until_options& options = default_read_until_options()) noexcept { + return read_until_match(std::find(it_, end_, delimiter), options); + } + + /** + * Reads multiple chars from the stream until a given char sequence as delimiter is reached or EOF (configurable). + * @param delimiter The char sequence. + * @param options The read until options. + * @return invalid_data if the delimiter hasn't been found and ignore_eof is set to true or EOF if the stream is + * empty. + */ + constexpr result read_until_str(const std::string_view& delimiter, + const read_until_options& options = default_read_until_options()) { + return read_until_pos(view_remaining().find(delimiter), options, delimiter.size()); + } + + /** + * Reads multiple chars from the stream until a char of a given group is reached or EOF (configurable). + * @param group The char group. + * @param options The read until options. + * @return invalid_data if no char has been found and ignore_eof is set to True or EOF if the stream is empty. + */ + constexpr result read_until_any_of( + const std::string_view& group, const read_until_options& options = default_read_until_options()) noexcept { + return read_until_pos(view_remaining().find_first_of(group), options); + } + + /** + * Reads multiple chars from the stream until no char of a given group is reached or EOF (configurable). + * @param group The char group. + * @param options The read until options. + * @return invalid_data if a char not in the group has been found and ignore_eof is set to True or EOF if the stream + * is empty. + */ + constexpr result read_until_none_of( + const std::string_view& group, const read_until_options& options = default_read_until_options()) noexcept { + return read_until_pos(view_remaining().find_first_not_of(group), options); + } + + /** + * Reads multiple chars from the stream until a given predicate returns true or EOF is reached (configurable). + * @param predicate The predicate taking a char and returning a bool. + * @param options The read until options. + * @return invalid_data if the predicate never returns true and ignore_eof is set to True or EOF if the stream is + * empty. + */ + template + requires(std::is_invocable_r_v) + constexpr result + read_until(const Predicate& predicate, const read_until_options& options = default_read_until_options()) noexcept( + std::is_nothrow_invocable_r_v) { + return read_until_match(std::find_if(it_, end_, predicate), options); + } + + /** + * Reads one char from the stream if the char matches the expected one. + * @param c The expected char. + * @return invalid_data if the chars don't match or EOF if the end of the stream has been reached. + */ + constexpr result read_if_match_char(const char c) noexcept { + if (it_ != end_) { + if (*it_ == c) { + ++it_; + return c; + } + return err::invalid_data; + } + return err::eof; + } + + /** + * Reads multiple chars from the stream if the chars matches the expected char sequence. + * @param sv The expected char sequence. + * @return invalid_data if the chars don't match or EOF if the end of the stream has been reached. + */ + constexpr result read_if_match_str(const std::string_view& sv) noexcept { + const size_t n = sv.size(); + if (static_cast(end_ - it_) < n) { + return err::eof; + } + if (detail::equal_n(it_, sv.begin(), n)) { + const std::string_view res{it_, n}; + it_ += n; + return res; + } + return err::invalid_data; + } + + private: + friend constexpr const char*& detail::get_it(reader& rdr) noexcept; + friend constexpr const char* detail::get_end(reader& rdr) noexcept; + + // Helper function since GCC and Clang complain about "member initializer for '...' needed within definition of + // enclosing class". Which is a bug. + [[nodiscard]] static constexpr read_until_options default_read_until_options() noexcept { + return {}; + } + + template + constexpr result parse_sign_and_int(const int base) noexcept { + EMIO_TRY(const bool is_negative, detail::parse_sign(*this)); + return detail::parse_int(*this, base, is_negative); + } + + constexpr result read_until_pos(size_t pos, const read_until_options& options, + const size_type delimiter_size = 1) noexcept { + const char* match = end_; + if (pos != npos) { + match = it_ + pos; + } + return read_until_match(match, options, delimiter_size); + } + + constexpr result read_until_match(const char* match, const read_until_options& options, + const size_type delimiter_size = 1) noexcept { + if (it_ == end_) { + return err::eof; + } + const char* const begin = it_; + if (match == end_) { + if (!options.ignore_eof) { + it_ = end_; + return std::string_view{begin, end_}; + } + return err::invalid_data; + } + it_ = match; + if (!options.keep_delimiter) { + it_ += delimiter_size; + } + if (options.include_delimiter) { + match += delimiter_size; + } + return std::string_view{begin, match}; + } + + const char* begin_{}; + const char* it_{}; + const char* end_{}; +}; + +namespace detail { + +inline constexpr const char*& get_it(reader& rdr) noexcept { + return rdr.it_; +} + +inline constexpr const char* get_end(reader& rdr) noexcept { + return rdr.end_; +} + +inline constexpr result parse_sign(reader& in) noexcept { + bool is_negative = false; + EMIO_TRY(const char c, in.peek()); + if (c == '+') { + in.pop(); + } else if (c == '-') { + is_negative = true; + in.pop(); + } + return is_negative; +} + +template +constexpr result parse_int(reader& in, const int base, const bool is_negative) noexcept { + if (!is_valid_number_base(base)) { + return err::invalid_argument; + } + + EMIO_TRY(const char c, in.read_char()); + std::optional digit = char_to_digit(c, base); + if (!digit) { + return err::invalid_data; + } + + if constexpr (std::is_unsigned_v) { + if (is_negative) { + return err::out_of_range; + } + } + + T value{}; + T maybe_overflowed_value{}; + const auto has_next_digit = [&]() noexcept { + value = maybe_overflowed_value; + + const result res = in.peek(); + if (!res) { + return false; + } + digit = detail::char_to_digit(res.value(), base); + if (!digit) { + return false; + } + in.pop(); + maybe_overflowed_value = value * static_cast(base); + return true; + }; + + if constexpr (std::is_signed_v) { + if (is_negative) { + while (true) { + maybe_overflowed_value -= static_cast(*digit); + if (maybe_overflowed_value > value) { + return err::out_of_range; + } + if (!has_next_digit()) { + return value; + } + } + } + } + while (true) { + maybe_overflowed_value += static_cast(*digit); + if (maybe_overflowed_value < value) { + return err::out_of_range; + } + if (!has_next_digit()) { + return value; + } + } +} + +} // namespace detail + +} // namespace emio + +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include + +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include +#include +#include +#include +#include +#include + +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include +#include +#include +#include + +namespace emio::detail { + +/** + * A constexpr vector with the bare minimum implementation and inlined storage. + * @tparam Char The character type. + * @tparam StorageSize The size of the inlined storage. + */ +template +class ct_vector { + public: + constexpr ct_vector() noexcept { + if (EMIO_Z_INTERNAL_IS_CONST_EVAL) { + fill_n(storage_.data(), storage_.size(), 0); + } + } + + ct_vector(const ct_vector&) = delete; + ct_vector(ct_vector&&) = delete; + ct_vector& operator=(const ct_vector&) = delete; + ct_vector& operator=(ct_vector&&) = delete; + + constexpr ~ct_vector() noexcept { + if (hold_external()) { + delete[] data_; // NOLINT(cppcoreguidelines-owning-memory) + } + } + + constexpr void reserve(size_t new_size) noexcept { + if (new_size < StorageSize && !hold_external()) { + size_ = new_size; + return; + } + + // Heavy pointer arithmetic because high level containers are not yet ready to use at constant evaluation. + if (capacity_ < new_size) { + // NOLINTNEXTLINE(bugprone-unhandled-exception-at-new): char types cannot throw + Char* new_data = new Char[new_size]; // NOLINT(cppcoreguidelines-owning-memory) + copy_n(data_, size_, new_data); + if (EMIO_Z_INTERNAL_IS_CONST_EVAL) { + // Required at compile-time because another reserve could happen without previous write to the data. + fill_n(new_data + size_, new_size - size_, 0); + } + std::swap(new_data, data_); + capacity_ = new_size; + if (new_data != storage_.data()) { + delete[] new_data; // NOLINT(cppcoreguidelines-owning-memory) + } + } + size_ = new_size; + } + + [[nodiscard]] constexpr size_t capacity() const noexcept { + return capacity_; + } + + [[nodiscard]] constexpr size_t size() const noexcept { + return size_; + } + + [[nodiscard]] constexpr Char* data() noexcept { + return data_; + } + + [[nodiscard]] constexpr const Char* data() const noexcept { + return data_; + } + + private: + [[nodiscard]] constexpr bool hold_external() const noexcept { + return data_ != storage_.data() && data_ != nullptr; + } + + std::array storage_; + Char* data_{storage_.data()}; + size_t size_{}; + size_t capacity_{StorageSize}; +}; + +} // namespace emio::detail + +namespace emio { + +/// The default cache size of buffers with an internal cache. +inline constexpr size_t default_cache_size{128}; + +/** + * This class provides the basic API and functionality for receiving a contiguous memory region of chars to write into. + * @note Use a specific subclass for a concrete instantiation. + */ +class buffer { + public: + buffer(const buffer& other) = delete; + buffer(buffer&& other) = delete; + buffer& operator=(const buffer& other) = delete; + buffer& operator=(buffer&& other) = delete; + virtual constexpr ~buffer() noexcept = default; + + /** + * Returns a write area with the requested size on success. + * @param size The size the write area should have. + * @return The write area with the requested size on success or eof if no write area is available. + */ + constexpr result> get_write_area_of(size_t size) noexcept { + EMIO_TRY(const std::span area, get_write_area_of_max(size)); + if (area.size() < size) { + used_ -= area.size(); + return err::eof; + } + return area; + } + + /** + * Returns a write area which may be smaller than the requested size. + * @note This function should be used to support subclasses with a limited internal buffer. + * E.g. Writing a long string in chunks. + * @param size The size the write area should maximal have. + * @return The write area with the requested size as maximum on success or eof if no write area is available. + */ + constexpr result> get_write_area_of_max(size_t size) noexcept { + // If there is enough remaining capacity in the current write area, return it. + // Otherwise, request a new write area from the concrete implementation. + // There is a special case for fixed size buffers. Since they cannot grow, they simply return the + // remaining capacity or EOF, if hitting zero capacity. + + const size_t remaining_capacity = area_.size() - used_; + if (remaining_capacity >= size || fixed_size_ == fixed_size::yes) { + if (remaining_capacity == 0 && size != 0) { + return err::eof; + } + const size_t max_size = std::min(remaining_capacity, size); + const std::span area = area_.subspan(used_, max_size); + used_ += max_size; + return area; + } + EMIO_TRY(const std::span area, request_write_area(used_, size)); + used_ += area.size(); + return area; + } + + protected: + /// Flag to indicate if the buffer's size is fixed and cannot grow. + enum class fixed_size : bool { no, yes }; + + /** + * Constructs the buffer. + * @brief fixed Flag to indicate if the buffer's size is fixed and cannot grow. + */ + constexpr explicit buffer(fixed_size fixed = fixed_size::no) noexcept : fixed_size_{fixed} {} + + /** + * Requests a write area of the given size from a subclass. + * @param used Already written characters into the current write area. + * @param size The requested size of a new write area. + * @return The write area with the requested size as maximum on success or eof if no write area is available. + */ + virtual constexpr result> request_write_area(const size_t used, const size_t size) noexcept { + static_cast(used); // Keep params for documentation. + static_cast(size); + return err::eof; + } + + /** + * Sets a new write area in the base class object to use. + * @param area The new write area. + */ + constexpr void set_write_area(const std::span area) noexcept { + area_ = area; + used_ = 0; + } + + /** + * Returns the count of written characters of the current hold write area. + * @return The count. + */ + [[nodiscard]] constexpr size_t get_used_count() const noexcept { + return used_; + } + + private: + fixed_size fixed_size_{fixed_size::no}; + size_t used_{}; + std::span area_{}; +}; + +/** + * This class fulfills the buffer API by providing an endless growing buffer. + * @tparam StorageSize The size of the internal storage used for small buffer optimization. + */ +template +class memory_buffer final : public buffer { + public: + /** + * Constructs and initializes the buffer with the internal storage size. + */ + constexpr memory_buffer() noexcept : memory_buffer{0} {} + + /** + * Constructs and initializes the buffer with the given capacity. + * @param capacity The initial capacity. + */ + constexpr explicit memory_buffer(const size_t capacity) noexcept { + // Request at least the internal storage size. + static_cast(request_write_area(0, std::max(vec_.capacity(), capacity))); + } + + constexpr memory_buffer(const memory_buffer&) = default; + constexpr memory_buffer(memory_buffer&&) noexcept = default; + constexpr memory_buffer& operator=(const memory_buffer&) = default; + constexpr memory_buffer& operator=(memory_buffer&&) noexcept = default; + constexpr ~memory_buffer() override = default; + + /** + * Obtains a view over the underlying string object. + * @return The view. + */ + [[nodiscard]] constexpr std::string_view view() const noexcept { + return {vec_.data(), used_ + this->get_used_count()}; + } + + /** + * Obtains a copy of the underlying string object. + * @return The string. + */ + [[nodiscard]] std::string str() const { + return std::string{view()}; + } + + protected: + constexpr result> request_write_area(const size_t used, const size_t size) noexcept override { + const size_t new_size = vec_.size() + size; + vec_.reserve(new_size); + used_ += used; + const std::span area{vec_.data() + used_, size}; + this->set_write_area(area); + return area; + } + + private: + size_t used_{}; + detail::ct_vector vec_{}; +}; + +/** + * This class fulfills the buffer API by using a span over an contiguous range. + */ +class span_buffer : public buffer { + public: + /** + * Constructs and initializes the buffer with an empty span. + */ + constexpr span_buffer() : buffer{fixed_size::yes} {}; + + /** + * Constructs and initializes the buffer with the given span. + * @param span The span. + */ + constexpr explicit span_buffer(const std::span span) noexcept : buffer{fixed_size::yes}, span_{span} { + this->set_write_area(span_); + } + + constexpr span_buffer(const span_buffer&) = delete; + constexpr span_buffer(span_buffer&&) noexcept = delete; + constexpr span_buffer& operator=(const span_buffer&) = delete; + constexpr span_buffer& operator=(span_buffer&&) noexcept = delete; + constexpr ~span_buffer() override = default; + + /** + * Obtains a view over the underlying string object. + * @return The view. + */ + [[nodiscard]] constexpr std::string_view view() const noexcept { + return {span_.data(), this->get_used_count()}; + } + + /** + * Obtains a copy of the underlying string object. + * @return The string. + */ + [[nodiscard]] std::string str() const { + return std::string{view()}; + } + + private: + std::span span_; +}; + +/** + * This class fulfills the buffer API by providing a fixed-size storage. + * @tparam StorageSize The size of the storage. + */ +template +class static_buffer final : private std::array, public span_buffer { + public: + /** + * Constructs and initializes the buffer with the storage. + */ + constexpr static_buffer() noexcept : span_buffer{std::span{*this}} {} + + constexpr static_buffer(const static_buffer&) = delete; + constexpr static_buffer(static_buffer&&) noexcept = delete; + constexpr static_buffer& operator=(const static_buffer&) = delete; + constexpr static_buffer& operator=(static_buffer&&) noexcept = delete; + constexpr ~static_buffer() override = default; + + // Note: We inherit from std::array to put the storage lifetime before span_buffer. + // Clang will otherwise complain if the storage is a member variable and used during compile-time. +}; + +namespace detail { + +// Extracts a reference to the container from back_insert_iterator. +template +Container& get_container(std::back_insert_iterator it) noexcept { + using bi_iterator = std::back_insert_iterator; + struct accessor : bi_iterator { + accessor(bi_iterator iter) : bi_iterator(iter) {} + using bi_iterator::container; + }; + return *accessor{it}.container; +} + +// Helper struct to get the value type of different iterators. +template +struct get_value_type { + using type = typename std::iterator_traits::value_type; +}; + +template +struct get_value_type> { + using type = typename Container::value_type; +}; + +template +struct get_value_type> { + using type = Char; +}; + +template +using get_value_type_t = typename get_value_type::type; + +template +constexpr auto copy_str(InputIt it, InputIt end, OutputIt out) -> OutputIt { + while (it != end) { + *out++ = static_cast(*it++); + } + return out; +} + +} // namespace detail + +/** + * This class template is used to create a buffer around different iterator types. + */ +template +class iterator_buffer; + +/** + * This class fulfills the buffer API by using an output iterator and an internal cache. + * @tparam Iterator The output iterator type. + * @tparam CacheSize The size of the internal cache. + */ +template + requires(std::input_or_output_iterator && + std::output_iterator>) +class iterator_buffer final : public buffer { + public: + /** + * Constructs and initializes the buffer with the given output iterator. + * @param it The output iterator. + */ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): cache_ can be left uninitialized + constexpr explicit iterator_buffer(Iterator it) noexcept : it_{it} { + this->set_write_area(cache_); + } + + iterator_buffer(const iterator_buffer&) = delete; + iterator_buffer(iterator_buffer&&) = delete; + iterator_buffer& operator=(const iterator_buffer&) = delete; + iterator_buffer& operator=(iterator_buffer&&) = delete; + ~iterator_buffer() override = default; + + /** + * Flushes the internal cache to the output iterator. + * @return Always succeeds. + */ + constexpr result flush() noexcept { + it_ = detail::copy_str(cache_.data(), cache_.data() + this->get_used_count(), it_); + this->set_write_area(cache_); + return success; + } + + /** + * Flushes and returns the output iterator at the next write position. + * @return The output iterator. + */ + constexpr Iterator out() noexcept { + flush().assume_value(); // Will never fail. + return it_; + } + + protected: + constexpr result> request_write_area(const size_t /*used*/, const size_t size) noexcept override { + flush().assume_value(); // Will never fail. + const std::span area{cache_}; + this->set_write_area(area); + if (size > cache_.size()) { + return area; + } + return area.subspan(0, size); + } + + private: + Iterator it_; + std::array cache_; +}; + +/** + * This class fulfills the buffer API by using an output pointer. + * @tparam Iterator The output iterator type. + */ +template + requires(std::input_or_output_iterator && + std::output_iterator>) +class iterator_buffer final : public buffer { + public: + /** + * Constructs and initializes the buffer with the given output pointer. + * @param ptr The output pointer. + */ + constexpr explicit iterator_buffer(OutputPtr* ptr) noexcept : ptr_{ptr} { + this->set_write_area({ptr, std::numeric_limits::max()}); + } + + iterator_buffer(const iterator_buffer&) = delete; + iterator_buffer(iterator_buffer&&) = delete; + iterator_buffer& operator=(const iterator_buffer&) = delete; + iterator_buffer& operator=(iterator_buffer&&) = delete; + ~iterator_buffer() override = default; + + /** + * Does nothing. Kept for uniformity with other iterator_buffer implementations. + * @return Always succeeds. + */ + constexpr result flush() noexcept { + // Nothing. + return success; + } + + /** + * Returns the output pointer at the next write position. + * @return The output pointer. + */ + constexpr OutputPtr* out() noexcept { + return ptr_ + this->get_used_count(); + } + + private: + OutputPtr* ptr_; +}; + +/** + * This class fulfills the buffer API by using the container of an contiguous back-insert iterator. + * @tparam Container The container type of the back-insert iterator. + * @tparam Capacity The minimum initial requested capacity of the container. + */ +template + requires std::contiguous_iterator +class iterator_buffer, Capacity> final : public buffer { + public: + /** + * Constructs and initializes the buffer with the given back-insert iterator. + * @param it The back-insert iterator. + */ + constexpr explicit iterator_buffer(std::back_insert_iterator it) noexcept + : container_{detail::get_container(it)} { + static_cast(request_write_area(0, std::min(container_.capacity(), Capacity))); + } + + iterator_buffer(const iterator_buffer&) = delete; + iterator_buffer(iterator_buffer&&) = delete; + iterator_buffer& operator=(const iterator_buffer&) = delete; + iterator_buffer& operator=(iterator_buffer&&) = delete; + ~iterator_buffer() override = default; + + /** + * Flushes the back-insert iterator by adjusting the size. + * @return Always succeeds. + */ + constexpr result flush() noexcept { + container_.resize(used_ + this->get_used_count()); + return success; + } + + /** + * Flushes and returns the back-insert iterator. + * @return The back-insert iterator. + */ + constexpr std::back_insert_iterator out() noexcept { + flush().assume_value(); // Will never fail. + return std::back_inserter(container_); + } + + protected: + constexpr result> request_write_area(const size_t used, const size_t size) noexcept override { + const size_t new_size = container_.size() + size; + container_.resize(new_size); + used_ += used; + const std::span area{container_.data() + used_, new_size}; + this->set_write_area(area); + return area.subspan(0, size); + } + + private: + size_t used_{}; + Container& container_; +}; + +template +iterator_buffer(Iterator&&) -> iterator_buffer>; + +/** + * This class fulfills the buffer API by using a file stream and an internal cache. + * @tparam CacheSize The size of the internal cache. + */ +template +class file_buffer final : public buffer { + public: + /** + * Constructs and initializes the buffer with the given file stream. + * @param file The file stream. + */ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): cache_ can be left uninitialized + constexpr explicit file_buffer(std::FILE* file) noexcept : file_{file} { + this->set_write_area(cache_); + } + + file_buffer(const file_buffer&) = delete; + file_buffer(file_buffer&&) = delete; + file_buffer& operator=(const file_buffer&) = delete; + file_buffer& operator=(file_buffer&&) = delete; + ~file_buffer() override = default; + + /** + * Flushes the internal cache to the file stream. + * @note Does not flush the file stream itself! + */ + result flush() noexcept { + const size_t written = std::fwrite(cache_.data(), sizeof(char), this->get_used_count(), file_); + if (written != this->get_used_count()) { + return err::eof; + } + this->set_write_area(cache_); + return success; + } + + protected: + result> request_write_area(const size_t /*used*/, const size_t size) noexcept override { + EMIO_TRYV(flush()); + const std::span area{cache_}; + this->set_write_area(area); + if (size > cache_.size()) { + return area; + } + return area.subspan(0, size); + } + + private: + std::FILE* file_; + std::array cache_; +}; + +/** + * This class fulfills the buffer API by using a primary buffer and an internal cache. + * Only a limited amount of characters is written to the primary buffer. The remaining characters are truncated. + * @tparam CacheSize The size of the internal cache. + */ +template +class truncating_buffer final : public buffer { + public: + /** + * Constructs and initializes the buffer with the given primary buffer and limit. + * @param primary The primary buffer. + * @param limit The limit. + */ + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): cache_ can be left uninitialized. + constexpr explicit truncating_buffer(buffer& primary, size_t limit) : primary_{primary}, limit_{limit} { + this->set_write_area(cache_); + } + + truncating_buffer(const truncating_buffer&) = delete; + truncating_buffer(truncating_buffer&&) = delete; + truncating_buffer& operator=(const truncating_buffer&) = delete; + truncating_buffer& operator=(truncating_buffer&&) = delete; + constexpr ~truncating_buffer() noexcept override = default; + + /** + * Returns the count of the total (not truncated) written characters. + * @return The count. + */ + [[nodiscard]] constexpr size_t count() const noexcept { + return used_ + this->get_used_count(); + } + + /** + * Flushes the internal cache to the primary buffer. + */ + [[nodiscard]] constexpr result flush() noexcept { + size_t bytes_to_write = get_used_count(); + used_ += bytes_to_write; + while (written_ < limit_ && bytes_to_write > 0) { + EMIO_TRY(const auto area, primary_.get_write_area_of_max(std::min(bytes_to_write, limit_ - written_))); + detail::copy_n(cache_.begin(), area.size(), area.data()); + written_ += area.size(); + bytes_to_write -= area.size(); + } + this->set_write_area(cache_); + return success; + } + + protected: + constexpr result> request_write_area(const size_t /*used*/, const size_t size) noexcept override { + EMIO_TRYV(flush()); + const std::span area{cache_}; + this->set_write_area(area); + if (size > cache_.size()) { + return area; + } + return area.subspan(0, size); + } + + private: + buffer& primary_; + size_t limit_; + size_t written_{}; + size_t used_{}; + std::array cache_; +}; + +namespace detail { + +/** + * A buffer that counts the number of characters written. Discards the output. + * @tparam CacheSize The size of the internal cache. + */ +// NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): cache_ can be left uninitialized. +template +class counting_buffer final : public buffer { + public: + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init): cache_ can be left uninitialized. + constexpr counting_buffer() noexcept = default; + constexpr counting_buffer(const counting_buffer&) = delete; + constexpr counting_buffer(counting_buffer&&) noexcept = delete; + constexpr counting_buffer& operator=(const counting_buffer&) = delete; + constexpr counting_buffer& operator=(counting_buffer&&) noexcept = delete; + constexpr ~counting_buffer() noexcept override = default; + + /** + * Calculates the number of Char's that were written. + * @return The number of Char's. + */ + [[nodiscard]] constexpr size_t count() const noexcept { + return used_ + this->get_used_count(); + } + + protected: + constexpr result> request_write_area(const size_t used, const size_t size) noexcept override { + used_ += used; + const std::span area{cache_}; + this->set_write_area(area); + if (size > cache_.size()) { + return area; + } + return area.subspan(0, size); + } + + private: + size_t used_{}; + std::array cache_; +}; + +} // namespace detail + +} // namespace emio + +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include +#include +#include +#include +#include + +namespace emio::detail { + +inline constexpr bool needs_escape(uint32_t cp) noexcept { + return cp < 0x20 || cp >= 0x7f || cp == '\'' || cp == '"' || cp == '\\'; +} + +inline constexpr size_t count_size_when_escaped(std::string_view sv) noexcept { + size_t count = 0; + for (const char c : sv) { + if (!needs_escape(static_cast(c))) { + count += 1; + } else if (c == '\n' || c == '\r' || c == '\t' || c == '\\' || c == '\'' || c == '"') { + count += 2; + } else { + count += 2 + 2 * sizeof(char); // \xAB... + } + } + return count; +} + +/* + * Class which helps to escape a long string in smaller chunks. + */ +class write_escaped_helper { + public: + constexpr write_escaped_helper(std::string_view sv) noexcept : src_it_{sv.begin()}, src_end_{sv.end()} {} + + [[nodiscard]] constexpr size_t write_escaped(std::span area) noexcept { + char* dst_it = area.data(); + const char* const dst_end = area.data() + area.size(); + + // Write remainder from temporary buffer. + const auto write_remainder = [&, this]() noexcept { + while (remainder_it_ != remainder_end_ && dst_it != dst_end) { + *(dst_it++) = *(remainder_it_++); + } + }; + write_remainder(); + + while (src_it_ != src_end_) { + if (dst_it == dst_end) { + return static_cast(dst_it - area.data()); + } + const char c = *src_it_++; + if (!needs_escape(static_cast(c))) { + *(dst_it++) = c; + } else { + *(dst_it++) = '\\'; + const auto remaining_space = static_cast(dst_end - dst_it); + if (remaining_space >= 3) { + dst_it = write_escaped(c, dst_it); + } else { + // Write escaped sequence to remainder. + remainder_it_ = remainder_storage_.begin(); + remainder_end_ = write_escaped(c, remainder_it_); + // Write as much as possible into dst. + write_remainder(); + } + } + } + return static_cast(dst_it - area.data()); + } + + private: + [[nodiscard]] static inline constexpr char* write_escaped(const char c, char* out) noexcept { + switch (c) { + case '\n': + *(out++) = 'n'; + return out; + case '\r': + *(out++) = 'r'; + return out; + case '\t': + *(out++) = 't'; + return out; + case '\\': + *(out++) = '\\'; + return out; + case '\'': + *(out++) = '\''; + return out; + case '"': + *(out++) = '"'; + return out; + default: { + // Escape char zero filled like: \x05 + *(out++) = 'x'; + const auto abs = detail::to_absolute(detail::to_unsigned(c)); + const size_t number_of_digits = count_digits<16>(abs); + // Fill up with zeros. + for (size_t i = 0; i < 2 * sizeof(char) - number_of_digits; i++) { + *(out++) = '0'; + } + out += to_signed(number_of_digits); + write_number(abs, 16, false, out); + return out; + } + } + } + + const char* src_it_; // Input to encode. + const char* src_end_; + std::array remainder_storage_{}; // Remainder containing data for the next iteration. + char* remainder_it_{}; + char* remainder_end_{}; +}; + +inline constexpr result write_str_escaped(buffer& buf, std::string_view sv, size_t escaped_size, + const char quote) { + // Perform escaping in multiple chunks, to support buffers with an internal cache. + detail::write_escaped_helper helper{sv}; + EMIO_TRY(auto area, buf.get_write_area_of_max(escaped_size + 2 /*both quotes*/)); + // Start quote. + area[0] = quote; + area = area.subspan(1); + + while (true) { + const size_t written = helper.write_escaped(area); + escaped_size -= written; + if (escaped_size == 0) { + area = area.subspan(written); + break; + } + EMIO_TRY(area, buf.get_write_area_of_max(escaped_size + 1 /*end quote*/)); + } + if (area.empty()) { + EMIO_TRY(area, buf.get_write_area_of_max(1 /*end quote*/)); + } + // End quote. + area[0] = quote; + return success; +} + +} // namespace emio::detail + +namespace emio { + +/** + * This class operates on a buffer and allows writing sequences of characters or other kinds of data into it. + */ +class writer { + public: + /** + * Constructs a writer with a given buffer. + * @param buf The buffer. + */ + constexpr writer(buffer& buf) noexcept : buf_{buf} {} + + /** + * Returns the buffer. + * @return The buffer. + */ + [[nodiscard]] constexpr buffer& get_buffer() noexcept { + return buf_; + } + + /** + * Writes a character into the buffer. + * @param c The character. + * @return EOF if the buffer is to small. + */ + constexpr result write_char(const char c) noexcept { + EMIO_TRY(const auto area, buf_.get_write_area_of(1)); + area[0] = c; + return success; + } + + /** + * Writes a character n times into the buffer. + * @param c The character. + * @param n The number of times the character should be written. + * @return EOF if the buffer is to small. + */ + constexpr result write_char_n(const char c, const size_t n) noexcept { + // Perform write in multiple chunks, to support buffers with an internal cache. + size_t remaining_size = n; + while (remaining_size != 0) { + EMIO_TRY(const auto area, buf_.get_write_area_of_max(remaining_size)); + detail::fill_n(area.data(), area.size(), c); + remaining_size -= area.size(); + } + return success; + } + + /** + * Writes a character escaped into the buffer. + * @param c The character. + * @return EOF if the buffer is to small. + */ + constexpr result write_char_escaped(const char c) noexcept { + const std::string_view sv(&c, 1); + return detail::write_str_escaped(buf_, sv, detail::count_size_when_escaped(sv), '\''); + } + + /** + * Writes a char sequence into the buffer. + * @param sv The char sequence. + * @return EOF if the buffer is to small. + */ + constexpr result write_str(const std::string_view sv) noexcept { + // Perform write in multiple chunks, to support buffers with an internal cache. + const char* ptr = sv.data(); + size_t remaining_size = sv.size(); + while (remaining_size != 0) { + EMIO_TRY(const auto area, buf_.get_write_area_of_max(remaining_size)); + detail::copy_n(ptr, area.size(), area.data()); + remaining_size -= area.size(); + ptr += area.size(); + } + return success; + } + + /** + * Writes a char sequence escaped into the buffer. + * @param sv The char sequence. + * @return EOF if the buffer is to small. + */ + constexpr result write_str_escaped(const std::string_view sv) noexcept { + return detail::write_str_escaped(buf_, sv, detail::count_size_when_escaped(sv), '"'); + } + + /** + * Format options for writing integers. + */ + struct write_int_options { + int base{10}; ///< The output base of the integer. Must be greater equal 2 and less equal 36. + bool upper_case{false}; ///< If true, the letters are upper case, otherwise lower case. + }; + + /** + * Writes an integer into the buffer. + * @param integer The integer. + * @param options The integer options. + * @return invalid_argument if the requested output base is not supported or EOF if the buffer is to small. + */ + template + requires(std::is_integral_v) + constexpr result write_int(const T integer, + const write_int_options& options = default_write_int_options()) noexcept { + // Reduce code generation by upcasting the integer. + return write_int_impl(detail::integer_upcast(integer), options); + } + + private: + // Helper function since GCC and Clang complain about "member initializer for '...' needed within definition of + // enclosing class". Which is a bug. + static constexpr write_int_options default_write_int_options() noexcept { + return {}; + } + + template + requires(std::is_integral_v) + constexpr result write_int_impl(const T integer, const write_int_options& options) noexcept { + if (!detail::is_valid_number_base(options.base)) { + return err::invalid_argument; + } + const auto abs_number = detail::to_absolute(integer); + const bool negative = detail::is_negative(integer); + const size_t number_of_digits = + detail::get_number_of_digits(abs_number, options.base) + static_cast(negative); + + EMIO_TRY(const auto area, buf_.get_write_area_of(number_of_digits)); + if (negative) { + area[0] = '-'; + } + detail::write_number(abs_number, options.base, options.upper_case, + area.data() + detail::to_signed(number_of_digits)); + return success; + } + + buffer& buf_; +}; + +} // namespace emio + +// +// Copyright (c) 2023 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include +#include + +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include +#include +#include +#include + +// +// Copyright (c) 2023 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include +#include +#include + +// +// Copyright (c) 2023 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#include +#include + +namespace emio::detail { + +inline constexpr bool check_if_plain_string(const std::string_view& s) noexcept { + if (EMIO_Z_INTERNAL_IS_CONST_EVAL) { + return s.find_first_of("{}"sv) == npos; + } else { + return std::memchr(s.data(), static_cast('{'), s.size()) == nullptr && + std::memchr(s.data(), static_cast('}'), s.size()) == nullptr; + } +} + +class validated_string_storage { + public: + template + static constexpr validated_string_storage from(const std::string_view& s) noexcept { + if constexpr (sizeof...(Args) == 0) { + if (check_if_plain_string(s)) { + return {{true, s}}; + } + } + if (Trait::template validate_string(s)) { + return {{false, s}}; + } + return {}; + } + + constexpr validated_string_storage() noexcept = default; + + /** + * Returns if it is just a plain string. + * @return True, if the string does not contain any escape sequences or replacement fields, otherwise false. + */ + [[nodiscard]] constexpr bool is_plain_str() const noexcept { + return str_.first; + } + + /** + * Returns the validated format/scan string. + * @return The view or invalid_format if the validation failed. + */ + constexpr result get() const noexcept { + return str_.second; + } + + private: + // NOLINTNEXTLINE(modernize-pass-by-value): false-positive since no dynamic allocation takes place + constexpr validated_string_storage(const std::pair>& str) noexcept : str_{str} {} + + // Wonder why pair and not two variables? Look at this bug report: https://github.com/llvm/llvm-project/issues/67731 + std::pair> str_{false, err::invalid_format}; +}; + +} // namespace emio::detail + +namespace emio::detail { + +/** + * Type erased argument to validate. + */ +template