diff --git a/CMakePresets.json b/CMakePresets.json index 12e6fa9..1b8035a 100644 --- a/CMakePresets.json +++ b/CMakePresets.json @@ -154,7 +154,10 @@ "ci-build", "ci-unix", "dev-mode" - ] + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Release" + } }, { "name": "ci-memcheck", @@ -225,6 +228,17 @@ "SIZE_COVERAGE_HTML_COMMAND": "", "SIZE_TOOL": "arm-none-eabi-size" } + }, + { + "name": "dev", + "inherits": [ + "ci-build", + "ci-unix", + "dev-mode" + ], + "cacheVariables": { + "CMAKE_BUILD_TYPE": "Debug" + } } ], "buildPresets": [ diff --git a/include/emio/detail/bignum.hpp b/include/emio/detail/bignum.hpp index de216c8..4185171 100644 --- a/include/emio/detail/bignum.hpp +++ b/include/emio/detail/bignum.hpp @@ -110,7 +110,7 @@ class bignum { /// Returns `true` if the bignum is zero. [[nodiscard]] constexpr bool is_zero() const noexcept { - return std::all_of(base_.begin(), base_.end(), [](uint32_t v) { + return std::all_of(base_.begin(), base_.begin() + size_, [](uint32_t v) { return v == 0; }); } @@ -143,9 +143,7 @@ class bignum { base_[i] = res.value; } EMIO_Z_DEV_ASSERT(!res.carry); - if (i > size_) { - size_ = i; - } + size_ = std::max(i, size_); return *this; } diff --git a/include/emio/detail/format/decode.hpp b/include/emio/detail/format/decode.hpp index 4867031..2d3be1b 100644 --- a/include/emio/detail/format/decode.hpp +++ b/include/emio/detail/format/decode.hpp @@ -25,7 +25,7 @@ struct finite_result_t { bool inclusive{}; }; -enum class category { zero, finite, infinity, nan }; +enum class category : uint8_t { zero, finite, infinity, nan }; struct decode_result_t { bool negative{}; diff --git a/include/emio/detail/format/dragon.hpp b/include/emio/detail/format/dragon.hpp index 4e458ae..0154d3b 100644 --- a/include/emio/detail/format/dragon.hpp +++ b/include/emio/detail/format/dragon.hpp @@ -62,7 +62,7 @@ struct format_fp_result_t { int16_t exp; }; -enum class format_exact_mode { significand_digits, decimal_point }; +enum class format_exact_mode : bool { significand_digits, decimal_point }; inline constexpr format_fp_result_t format_exact(const finite_result_t& dec, emio::buffer& buf, format_exact_mode mode, int16_t number_of_digits) noexcept { diff --git a/include/emio/detail/parser.hpp b/include/emio/detail/parser.hpp index a3340d7..cddc07c 100644 --- a/include/emio/detail/parser.hpp +++ b/include/emio/detail/parser.hpp @@ -20,7 +20,7 @@ namespace emio::detail { /** * Flag to enable/disable input validation. */ -enum class input_validation { enabled, disabled }; +enum class input_validation : bool { enabled, disabled }; inline constexpr uint8_t no_more_args = std::numeric_limits::max(); diff --git a/include/emio/detail/scan/float.hpp b/include/emio/detail/scan/float.hpp new file mode 100644 index 0000000..f783a17 --- /dev/null +++ b/include/emio/detail/scan/float.hpp @@ -0,0 +1,506 @@ +// +// Copyright (c) 2021 - present, Toni Neubert +// All rights reserved. +// +// For the license information refer to emio.hpp + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "../../result.hpp" +#include "../bignum.hpp" +#include "../conversion.hpp" + +namespace emio::detail::scan { + +/// Represents a parsed decimal number before conversion to float +struct decimal { + int64_t exponent; + uint64_t mantissa; + bool negative; + bool many_digits; +}; + +struct DecimalSeq { + static constexpr size_t MAX_DIGITS = 768; + static constexpr size_t MAX_DIGITS_WITHOUT_OVERFLOW = 19; + static constexpr int32_t DECIMAL_POINT_RANGE = 2047; + + /// The number of significant digits in the decimal. + size_t num_digits; + /// The offset of the decimal point in the significant digits. + int32_t decimal_point; + /// If the number of significant digits stored in the decimal is truncated. + bool truncated; + /// Buffer of the raw digits, in the range [0, 9]. + std::array digits; + + /// Trim trailing zeros from the buffer. + // FIXME(tgross35): this could be `.rev().position()` if perf is okay + void trim() { + // All of the following calls to `DecimalSeq::trim` can't panic because: + // + // 1. `parse_decimal` sets `num_digits` to a max of `DecimalSeq::MAX_DIGITS`. + // 2. `right_shift` sets `num_digits` to `write_index`, which is bounded by `num_digits`. + // 3. `left_shift` `num_digits` to a max of `DecimalSeq::MAX_DIGITS`. + // + // Trim is only called in `right_shift` and `left_shift`. + EMIO_Z_DEV_ASSERT(num_digits <= MAX_DIGITS); + while (num_digits != 0 && digits[num_digits - 1] == 0) { + num_digits -= 1; + } + } + + [[nodiscard]] uint64_t round() const { + if (num_digits == 0 || decimal_point < 0) { + return 0; + } else if (decimal_point >= static_cast(MAX_DIGITS_WITHOUT_OVERFLOW)) { + return 0xFFFFFFFFFFFFFFFFULL; + } + + size_t dp = static_cast(decimal_point); + uint64_t n = 0; + + for (size_t i = 0; i < dp; ++i) { + n *= 10; + if (i < num_digits) { + n += static_cast(digits[i]); + } + } + + bool round_up = false; + + if (dp < num_digits) { + round_up = digits[dp] >= 5; + if (digits[dp] == 5 && dp + 1 == num_digits) { + round_up = truncated || ((dp != 0) && ((1 & digits[dp - 1]) != 0)); + } + } + + if (round_up) { + n += 1; + } + return n; + } + + static size_t number_of_digits_decimal_left_shift(const DecimalSeq& d, size_t shift) { + static constexpr uint16_t TABLE[65] = { + 0x0000, 0x0800, 0x0801, 0x0803, 0x1006, 0x1009, 0x100D, 0x1812, 0x1817, 0x181D, 0x2024, 0x202B, 0x2033, + 0x203C, 0x2846, 0x2850, 0x285B, 0x3067, 0x3073, 0x3080, 0x388E, 0x389C, 0x38AB, 0x38BB, 0x40CC, 0x40DD, + 0x40EF, 0x4902, 0x4915, 0x4929, 0x513E, 0x5153, 0x5169, 0x5180, 0x5998, 0x59B0, 0x59C9, 0x61E3, 0x61FD, + 0x6218, 0x6A34, 0x6A50, 0x6A6D, 0x6A8B, 0x72AA, 0x72C9, 0x72E9, 0x7B0A, 0x7B2B, 0x7B4D, 0x8370, 0x8393, + 0x83B7, 0x83DC, 0x8C02, 0x8C28, 0x8C4F, 0x9477, 0x949F, 0x94C8, 0x9CF2, 0x051C, 0x051C, 0x051C, 0x051C, + }; + static constexpr uint8_t TABLE_POW5[0x051C] = { + 5, 2, 5, 1, 2, 5, 6, 2, 5, 3, 1, 2, 5, 1, 5, 6, 2, 5, 7, 8, 1, 2, 5, 3, 9, 0, 6, 2, 5, 1, 9, 5, 3, 1, 2, 5, 9, + 7, 6, 5, 6, 2, 5, 4, 8, 8, 2, 8, 1, 2, 5, 2, 4, 4, 1, 4, 0, 6, 2, 5, 1, 2, 2, 0, 7, 0, 3, 1, 2, 5, 6, 1, 0, 3, + 5, 1, 5, 6, 2, 5, 3, 0, 5, 1, 7, 5, 7, 8, 1, 2, 5, 1, 5, 2, 5, 8, 7, 8, 9, 0, 6, 2, 5, 7, 6, 2, 9, 3, 9, 4, 5, + 3, 1, 2, 5, 3, 8, 1, 4, 6, 9, 7, 2, 6, 5, 6, 2, 5, 1, 9, 0, 7, 3, 4, 8, 6, 3, 2, 8, 1, 2, 5, 9, 5, 3, 6, 7, 4, + 3, 1, 6, 4, 0, 6, 2, 5, 4, 7, 6, 8, 3, 7, 1, 5, 8, 2, 0, 3, 1, 2, 5, 2, 3, 8, 4, 1, 8, 5, 7, 9, 1, 0, 1, 5, 6, + 2, 5, 1, 1, 9, 2, 0, 9, 2, 8, 9, 5, 5, 0, 7, 8, 1, 2, 5, 5, 9, 6, 0, 4, 6, 4, 4, 7, 7, 5, 3, 9, 0, 6, 2, 5, 2, + 9, 8, 0, 2, 3, 2, 2, 3, 8, 7, 6, 9, 5, 3, 1, 2, 5, 1, 4, 9, 0, 1, 1, 6, 1, 1, 9, 3, 8, 4, 7, 6, 5, 6, 2, 5, 7, + 4, 5, 0, 5, 8, 0, 5, 9, 6, 9, 2, 3, 8, 2, 8, 1, 2, 5, 3, 7, 2, 5, 2, 9, 0, 2, 9, 8, 4, 6, 1, 9, 1, 4, 0, 6, 2, + 5, 1, 8, 6, 2, 6, 4, 5, 1, 4, 9, 2, 3, 0, 9, 5, 7, 0, 3, 1, 2, 5, 9, 3, 1, 3, 2, 2, 5, 7, 4, 6, 1, 5, 4, 7, 8, + 5, 1, 5, 6, 2, 5, 4, 6, 5, 6, 6, 1, 2, 8, 7, 3, 0, 7, 7, 3, 9, 2, 5, 7, 8, 1, 2, 5, 2, 3, 2, 8, 3, 0, 6, 4, 3, + 6, 5, 3, 8, 6, 9, 6, 2, 8, 9, 0, 6, 2, 5, 1, 1, 6, 4, 1, 5, 3, 2, 1, 8, 2, 6, 9, 3, 4, 8, 1, 4, 4, 5, 3, 1, 2, + 5, 5, 8, 2, 0, 7, 6, 6, 0, 9, 1, 3, 4, 6, 7, 4, 0, 7, 2, 2, 6, 5, 6, 2, 5, 2, 9, 1, 0, 3, 8, 3, 0, 4, 5, 6, 7, + 3, 3, 7, 0, 3, 6, 1, 3, 2, 8, 1, 2, 5, 1, 4, 5, 5, 1, 9, 1, 5, 2, 2, 8, 3, 6, 6, 8, 5, 1, 8, 0, 6, 6, 4, 0, 6, + 2, 5, 7, 2, 7, 5, 9, 5, 7, 6, 1, 4, 1, 8, 3, 4, 2, 5, 9, 0, 3, 3, 2, 0, 3, 1, 2, 5, 3, 6, 3, 7, 9, 7, 8, 8, 0, + 7, 0, 9, 1, 7, 1, 2, 9, 5, 1, 6, 6, 0, 1, 5, 6, 2, 5, 1, 8, 1, 8, 9, 8, 9, 4, 0, 3, 5, 4, 5, 8, 5, 6, 4, 7, 5, + 8, 3, 0, 0, 7, 8, 1, 2, 5, 9, 0, 9, 4, 9, 4, 7, 0, 1, 7, 7, 2, 9, 2, 8, 2, 3, 7, 9, 1, 5, 0, 3, 9, 0, 6, 2, 5, + 4, 5, 4, 7, 4, 7, 3, 5, 0, 8, 8, 6, 4, 6, 4, 1, 1, 8, 9, 5, 7, 5, 1, 9, 5, 3, 1, 2, 5, 2, 2, 7, 3, 7, 3, 6, 7, + 5, 4, 4, 3, 2, 3, 2, 0, 5, 9, 4, 7, 8, 7, 5, 9, 7, 6, 5, 6, 2, 5, 1, 1, 3, 6, 8, 6, 8, 3, 7, 7, 2, 1, 6, 1, 6, + 0, 2, 9, 7, 3, 9, 3, 7, 9, 8, 8, 2, 8, 1, 2, 5, 5, 6, 8, 4, 3, 4, 1, 8, 8, 6, 0, 8, 0, 8, 0, 1, 4, 8, 6, 9, 6, + 8, 9, 9, 4, 1, 4, 0, 6, 2, 5, 2, 8, 4, 2, 1, 7, 0, 9, 4, 3, 0, 4, 0, 4, 0, 0, 7, 4, 3, 4, 8, 4, 4, 9, 7, 0, 7, + 0, 3, 1, 2, 5, 1, 4, 2, 1, 0, 8, 5, 4, 7, 1, 5, 2, 0, 2, 0, 0, 3, 7, 1, 7, 4, 2, 2, 4, 8, 5, 3, 5, 1, 5, 6, 2, + 5, 7, 1, 0, 5, 4, 2, 7, 3, 5, 7, 6, 0, 1, 0, 0, 1, 8, 5, 8, 7, 1, 1, 2, 4, 2, 6, 7, 5, 7, 8, 1, 2, 5, 3, 5, 5, + 2, 7, 1, 3, 6, 7, 8, 8, 0, 0, 5, 0, 0, 9, 2, 9, 3, 5, 5, 6, 2, 1, 3, 3, 7, 8, 9, 0, 6, 2, 5, 1, 7, 7, 6, 3, 5, + 6, 8, 3, 9, 4, 0, 0, 2, 5, 0, 4, 6, 4, 6, 7, 7, 8, 1, 0, 6, 6, 8, 9, 4, 5, 3, 1, 2, 5, 8, 8, 8, 1, 7, 8, 4, 1, + 9, 7, 0, 0, 1, 2, 5, 2, 3, 2, 3, 3, 8, 9, 0, 5, 3, 3, 4, 4, 7, 2, 6, 5, 6, 2, 5, 4, 4, 4, 0, 8, 9, 2, 0, 9, 8, + 5, 0, 0, 6, 2, 6, 1, 6, 1, 6, 9, 4, 5, 2, 6, 6, 7, 2, 3, 6, 3, 2, 8, 1, 2, 5, 2, 2, 2, 0, 4, 4, 6, 0, 4, 9, 2, + 5, 0, 3, 1, 3, 0, 8, 0, 8, 4, 7, 2, 6, 3, 3, 3, 6, 1, 8, 1, 6, 4, 0, 6, 2, 5, 1, 1, 1, 0, 2, 2, 3, 0, 2, 4, 6, + 2, 5, 1, 5, 6, 5, 4, 0, 4, 2, 3, 6, 3, 1, 6, 6, 8, 0, 9, 0, 8, 2, 0, 3, 1, 2, 5, 5, 5, 5, 1, 1, 1, 5, 1, 2, 3, + 1, 2, 5, 7, 8, 2, 7, 0, 2, 1, 1, 8, 1, 5, 8, 3, 4, 0, 4, 5, 4, 1, 0, 1, 5, 6, 2, 5, 2, 7, 7, 5, 5, 5, 7, 5, 6, + 1, 5, 6, 2, 8, 9, 1, 3, 5, 1, 0, 5, 9, 0, 7, 9, 1, 7, 0, 2, 2, 7, 0, 5, 0, 7, 8, 1, 2, 5, 1, 3, 8, 7, 7, 7, 8, + 7, 8, 0, 7, 8, 1, 4, 4, 5, 6, 7, 5, 5, 2, 9, 5, 3, 9, 5, 8, 5, 1, 1, 3, 5, 2, 5, 3, 9, 0, 6, 2, 5, 6, 9, 3, 8, + 8, 9, 3, 9, 0, 3, 9, 0, 7, 2, 2, 8, 3, 7, 7, 6, 4, 7, 6, 9, 7, 9, 2, 5, 5, 6, 7, 6, 2, 6, 9, 5, 3, 1, 2, 5, 3, + 4, 6, 9, 4, 4, 6, 9, 5, 1, 9, 5, 3, 6, 1, 4, 1, 8, 8, 8, 2, 3, 8, 4, 8, 9, 6, 2, 7, 8, 3, 8, 1, 3, 4, 7, 6, 5, + 6, 2, 5, 1, 7, 3, 4, 7, 2, 3, 4, 7, 5, 9, 7, 6, 8, 0, 7, 0, 9, 4, 4, 1, 1, 9, 2, 4, 4, 8, 1, 3, 9, 1, 9, 0, 6, + 7, 3, 8, 2, 8, 1, 2, 5, 8, 6, 7, 3, 6, 1, 7, 3, 7, 9, 8, 8, 4, 0, 3, 5, 4, 7, 2, 0, 5, 9, 6, 2, 2, 4, 0, 6, 9, + 5, 9, 5, 3, 3, 6, 9, 1, 4, 0, 6, 2, 5, + }; + + shift &= 63; + uint16_t x_a = TABLE[shift]; + uint16_t x_b = TABLE[shift + 1]; + size_t num_new_digits = static_cast(x_a >> 11); + size_t pow5_a = static_cast(0x7FF & x_a); + size_t pow5_b = static_cast(0x7FF & x_b); + const uint8_t* pow5 = &TABLE_POW5[pow5_a]; + + for (size_t i = 0; i < pow5_b - pow5_a; ++i) { + uint8_t p5 = pow5[i]; + if (i >= d.num_digits) { + return num_new_digits - 1; + } else if (d.digits[i] == p5) { + continue; + } else if (d.digits[i] < p5) { + return num_new_digits - 1; + } else { + return num_new_digits; + } + } + + return num_new_digits; + } + + /// Computes decimal * 2^shift. + void left_shift(size_t shift) { + if (num_digits == 0) { + return; + } + size_t num_new_digits = number_of_digits_decimal_left_shift(*this, shift); + size_t read_index = num_digits; + size_t write_index = num_digits + num_new_digits; + uint64_t n = 0; + + while (read_index != 0) { + read_index -= 1; + write_index -= 1; + n += (static_cast(digits[read_index]) << shift); + uint64_t quotient = n / 10; + uint64_t remainder = n - (10 * quotient); + if (write_index < MAX_DIGITS) { + digits[write_index] = static_cast(remainder); + } else if (remainder > 0) { + truncated = true; + } + n = quotient; + } + + while (n > 0) { + write_index -= 1; + uint64_t quotient = n / 10; + uint64_t remainder = n - (10 * quotient); + if (write_index < MAX_DIGITS) { + digits[write_index] = static_cast(remainder); + } else if (remainder > 0) { + truncated = true; + } + n = quotient; + } + + num_digits += num_new_digits; + + if (num_digits > MAX_DIGITS) { + num_digits = MAX_DIGITS; + } + + decimal_point += static_cast(num_new_digits); + trim(); + } + + /// Computes decimal * 2^-shift. + void right_shift(size_t shift) { + size_t read_index = 0; + size_t write_index = 0; + uint64_t n = 0; + while ((n >> shift) == 0) { + if (read_index < num_digits) { + n = (10 * n) + static_cast(digits[read_index]); + ++read_index; + } else if (n == 0) { + return; + } else { + while ((n >> shift) == 0) { + n *= 10; + ++read_index; + } + break; + } + } + decimal_point -= static_cast(read_index) - 1; + if (decimal_point < -static_cast(DECIMAL_POINT_RANGE)) { + // Reset to default, but without clearing digits. + num_digits = 0; + decimal_point = 0; + truncated = false; + return; + } + uint64_t mask = (static_cast(1) << shift) - 1; + while (read_index < num_digits) { + uint8_t new_digit = static_cast(n >> shift); + n = (10 * (n & mask)) + static_cast(digits[read_index]); + ++read_index; + digits[write_index] = new_digit; + ++write_index; + } + while (n > 0) { + uint8_t new_digit = static_cast(n >> shift); + n = 10 * (n & mask); + if (write_index < MAX_DIGITS) { + digits[write_index] = new_digit; + ++write_index; + } else if (new_digit > 0) { + truncated = true; + } + } + num_digits = write_index; + trim(); + } +}; + +struct biased_fp { + uint64_t mantissa; + int32_t power2; + + static constexpr biased_fp zero_pow2(int32_t p_biased) noexcept { + return biased_fp{.mantissa = 0, .power2 = p_biased}; + } +}; + +/// Parse a big integer representation of the float as a decimal. +constexpr std::pair parse_decimal_seq(std::string_view s) noexcept { + DecimalSeq d{}; + const char* const start = s.data(); + size_t start_len = s.size(); + + // Skip leading zeros + while (!s.empty() && s.front() == '0') { + s.remove_prefix(1); + } + + // Parse integer digits before decimal point + auto parse_digits = [](std::string_view& sv, DecimalSeq& dec) { + while (!sv.empty() && dec.num_digits < DecimalSeq::MAX_DIGITS && sv.front() >= '0' && sv.front() <= '9') { + dec.digits[dec.num_digits++] = static_cast(sv.front() - '0'); + sv.remove_prefix(1); + } + }; + parse_digits(s, d); + + // Parse fractional part if present + if (!s.empty() && s.front() == '.') { + s.remove_prefix(1); + // const char* first = s.data(); // TODO + size_t first_len = s.size(); + + // Skip leading zeros after decimal point if no digits yet + if (d.num_digits == 0) { + while (!s.empty() && s.front() == '0') { + s.remove_prefix(1); + } + } + + // Parse digits after decimal point + parse_digits(s, d); + + // Set decimal_point to number of digits after decimal point + d.decimal_point = static_cast(s.size() - first_len); + } + + if (d.num_digits != 0) { + // Ignore the trailing zeros if there are any + size_t n_trailing_zeros = 0; + size_t parsed_len = start_len - s.size(); + for (size_t i = 0; i < parsed_len; ++i) { + char c = start[parsed_len - 1 - i]; + if (c == '0') { + ++n_trailing_zeros; + } else if (c != '.') { + break; + } + } + d.decimal_point += static_cast(n_trailing_zeros); + if (d.num_digits >= n_trailing_zeros) { + d.num_digits -= n_trailing_zeros; + } else { + d.num_digits = 0; + } + d.decimal_point += static_cast(d.num_digits); + if (d.num_digits > DecimalSeq::MAX_DIGITS) { + d.truncated = true; + d.num_digits = DecimalSeq::MAX_DIGITS; + } + } + + // Parse exponent if present + if (!s.empty() && (s.front() == 'e' || s.front() == 'E')) { + s.remove_prefix(1); + bool neg_exp = false; + if (!s.empty() && (s.front() == '-' || s.front() == '+')) { + neg_exp = (s.front() == '-'); + s.remove_prefix(1); + } + int32_t exp_num = 0; + while (!s.empty() && s.front() >= '0' && s.front() <= '9') { + if (exp_num < 0x10000) { + exp_num = 10 * exp_num + static_cast(s.front() - '0'); + } + s.remove_prefix(1); + } + if (neg_exp) { + d.decimal_point -= exp_num; + } else { + d.decimal_point += exp_num; + } + } + + // Zero out remaining digits up to MAX_DIGITS_WITHOUT_OVERFLOW + for (size_t i = d.num_digits; i < DecimalSeq::MAX_DIGITS_WITHOUT_OVERFLOW; ++i) { + d.digits[i] = 0; + } + + return std::pair{d, s.data() - start}; +} + +/// Parse the significant digits and biased, binary exponent of a float. +/// +/// This is a fallback algorithm that uses a big-integer representation +/// of the float, and therefore is considerably slower than faster +/// approximations. However, it will always determine how to round +/// the significant digits to the nearest machine float, allowing +/// use to handle near half-way cases. +/// +/// Near half-way cases are halfway between two consecutive machine floats. +/// For example, the float `16777217.0` has a bitwise representation of +/// `100000000000000000000000 1`. Rounding to a single-precision float, +/// the trailing `1` is truncated. Using round-nearest, tie-even, any +/// value above `16777217.0` must be rounded up to `16777218.0`, while +/// any value before or equal to `16777217.0` must be rounded down +/// to `16777216.0`. These near-halfway conversions therefore may require +/// a large number of digits to unambiguously determine how to round. +/// +/// The algorithms described here are based on "Processing Long Numbers Quickly", +/// available here: . + +static constexpr size_t MAX_SHIFT = 60; +static constexpr size_t NUM_POWERS = 19; +static constexpr std::array POWERS = {0, 3, 6, 9, 13, 16, 19, 23, 26, 29, + 33, 36, 39, 43, 46, 49, 53, 56, 59}; + +struct Floater { + int32_t infinite_power{}; + int32_t exp_min{}; + int32_t sig_bits{}; +}; + +template +constexpr Floater get_floater() { + if constexpr (std::is_same_v) { + return Floater{ + .infinite_power = 255, + .exp_min = -126, + .sig_bits = 23, + }; + } else if constexpr (std::is_same_v) { + return Floater{ + .infinite_power = 2047, + .exp_min = -1022, + .sig_bits = 52, + }; + } +} + +constexpr biased_fp parse_long_mantissa(DecimalSeq d, const Floater& f) noexcept { + auto get_shift = [](size_t n) constexpr -> size_t { + return (n < NUM_POWERS) ? static_cast(POWERS[n]) : MAX_SHIFT; + }; + + const biased_fp fp_zero = biased_fp::zero_pow2(0); + const biased_fp fp_inf = biased_fp::zero_pow2(/* F:: */ f.infinite_power); + + // Short-circuit if the value can only be a literal 0 or infinity. + if (d.num_digits == 0 || d.decimal_point < -324) { + return fp_zero; + } else if (d.decimal_point >= 310) { + return fp_inf; + } + int32_t exp2 = 0; + // Shift right toward (1/2 ... 1]. + while (d.decimal_point > 0) { + const size_t n = static_cast(d.decimal_point); + const size_t shift = get_shift(n); + d.right_shift(shift); + if (d.decimal_point < -DecimalSeq::DECIMAL_POINT_RANGE) { + return fp_zero; + } + exp2 += static_cast(shift); + } + // Shift left toward (1/2 ... 1]. + while (d.decimal_point <= 0) { + size_t shift{}; + if (d.decimal_point == 0) { + const uint8_t digit = d.digits[0]; + if (digit >= 5) { + break; + } else if (digit == 0 || digit == 1) { + shift = 2; + } else { + shift = 1; + } + } else { + shift = get_shift(static_cast(-d.decimal_point)); + } + d.left_shift(shift); + if (d.decimal_point > DecimalSeq::DECIMAL_POINT_RANGE) { + return fp_inf; + } + exp2 -= static_cast(shift); + } + // We are now in the range [1/2 ... 1] but the binary format uses [1 ... 2]. + exp2 -= 1; + while (f.exp_min > exp2) { + size_t n = static_cast(f.exp_min - exp2); + n = std::min(n, MAX_SHIFT); + d.right_shift(n); + exp2 += static_cast(n); + } + if ((exp2 - f.exp_min + 1) >= f.infinite_power) { + return fp_inf; + } + // Shift the decimal to the hidden bit, and then round the value + // to get the high mantissa+1 bits. + d.left_shift(static_cast(f.sig_bits) + 1); + uint64_t mantissa = d.round(); + if (mantissa >= (1ULL << (f.sig_bits + 1))) { + // Rounding up overflowed to the carry bit, need to + // shift back to the hidden bit. + d.right_shift(1); + exp2 += 1; + mantissa = d.round(); + if ((exp2 - f.exp_min + 1) >= f.infinite_power) { + return fp_inf; + } + } + int32_t power2 = exp2 - f.exp_min + 1; + if (mantissa < (1ULL << f.sig_bits)) { + power2 -= 1; + } + // Zero out all the bits above the explicit mantissa bits. + mantissa &= (1ULL << f.sig_bits) - 1; + return biased_fp{mantissa, power2}; +} + +template +constexpr result parse_float(DecimalSeq d) noexcept { + const Floater f = get_floater(); + const auto x = parse_long_mantissa(d, f); + + /// Convert `biased_fp` to the closest machine float type. + uint64_t word = x.mantissa; + word |= static_cast(x.power2) << f.sig_bits; + Float result; + std::memcpy(&result, &word, sizeof(Float)); + return result; +} + +} // namespace emio::detail::scan \ No newline at end of file diff --git a/include/emio/detail/scan/scanner.hpp b/include/emio/detail/scan/scanner.hpp index 9b095c6..ab0dbf8 100644 --- a/include/emio/detail/scan/scanner.hpp +++ b/include/emio/detail/scan/scanner.hpp @@ -133,6 +133,13 @@ constexpr result read_arg(reader& original_in, const format_specs& specs, return success; } +template + requires(std::is_floating_point_v) +constexpr result read_arg(reader& in, const format_specs& /*specs*/, Arg& arg) noexcept { + EMIO_TRY(arg, in.parse_float()); + return success; +} + template requires(std::is_same_v) constexpr result read_arg(reader& in, const format_specs& /*unused*/, Arg& arg) noexcept { @@ -318,6 +325,21 @@ inline constexpr result check_string_specs(const format_specs& specs) noex return success; } +inline constexpr result check_floating_point_specs(const format_specs& specs) noexcept { + if (specs.alternate_form) { + return err::invalid_format; + } + switch (specs.type) { + case no_type: + case 'f': + case 'e': + case 'g': + return success; + default: + return err::invalid_format; + } +} + // // Type traits. // @@ -345,7 +367,8 @@ concept has_any_validate_function_v = template inline constexpr bool is_core_type_v = std::is_same_v || std::is_same_v || std::is_same_v || - std::is_same_v || std::is_same_v; + std::is_same_v || std::is_same_v || std::is_same_v || + std::is_same_v; } // namespace detail::scan diff --git a/include/emio/reader.hpp b/include/emio/reader.hpp index a2c5fa1..28ff2ad 100644 --- a/include/emio/reader.hpp +++ b/include/emio/reader.hpp @@ -12,6 +12,7 @@ #include #include "detail/conversion.hpp" +#include "detail/scan/float.hpp" #include "result.hpp" namespace emio { @@ -225,6 +226,19 @@ class reader { } } + template + requires(std::is_floating_point_v) + constexpr result parse_float() noexcept { + // Store current read position. + const char* const backup_it = it_; + const result res = parse_sign_and_float(); + if (!res) { + it_ = backup_it; + return res.assume_error(); + } + return res.assume_value(); + } + /** * Parse options for read_until operations. */ @@ -346,6 +360,18 @@ class reader { return detail::parse_int(*this, base, is_negative); } + template + constexpr result parse_sign_and_float() noexcept { + EMIO_TRY(const bool is_negative, detail::parse_sign(*this)); + auto [seq, consumed] = detail::scan::parse_decimal_seq(view_remaining()); + EMIO_TRY(T value, detail::scan::parse_float(seq)); + if (is_negative) { + value = -value; + } + it_ += consumed; + return value; + } + constexpr result read_until_pos(size_t pos, const read_until_options& options, const size_type delimiter_size = 1) noexcept { const char* match = end_; diff --git a/include/emio/result.hpp b/include/emio/result.hpp index 6fa47a2..96890d1 100644 --- a/include/emio/result.hpp +++ b/include/emio/result.hpp @@ -38,7 +38,7 @@ namespace emio { /** * A list of possible I/O errors. */ -enum class err { +enum class err : uint8_t { // 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). diff --git a/include/emio/scanner.hpp b/include/emio/scanner.hpp index 3bf7fad..6961cf5 100644 --- a/include/emio/scanner.hpp +++ b/include/emio/scanner.hpp @@ -65,8 +65,7 @@ class scanner { * This includes: * - char * - integral - * To be implemented: - * - floating-point types + * - floating-point (except for long double) * @tparam T The type. */ template @@ -80,6 +79,8 @@ class scanner { EMIO_TRYV(check_char_specs(specs)); } else if constexpr (std::is_integral_v) { EMIO_TRYV(check_integral_specs(specs)); + } else if constexpr (std::is_floating_point_v) { + EMIO_TRYV(check_floating_point_specs(specs)); } else { static_assert(detail::always_false_v, "Unknown core type!"); } @@ -99,7 +100,7 @@ class scanner { }; /** - * Scanner for integral types which are not core types. + * Scanner for arithmetic types which are not core types. */ template requires(std::is_integral_v && !std::is_same_v && !detail::scan::is_core_type_v) diff --git a/test/unit_test/CMakeLists.txt b/test/unit_test/CMakeLists.txt index c294ca0..dd7ecc2 100644 --- a/test/unit_test/CMakeLists.txt +++ b/test/unit_test/CMakeLists.txt @@ -11,33 +11,33 @@ if (PROJECT_IS_TOP_LEVEL) endif () add_executable(emio_test - detail/test_bignum.cpp - detail/test_bitset.cpp - detail/test_conversion.cpp - detail/test_ct_vector.cpp - detail/test_decode.cpp - detail/test_dragon.cpp - detail/test_utf.cpp - test_buffer.cpp - test_dynamic_format_spec.cpp - test_format.cpp - test_format_api.cpp - test_format_as.cpp - test_format_emio_vs_fmt.cpp - test_format_string.cpp - test_format_to_api.cpp - test_format_could_fail_api.cpp - test_format_to_n_api.cpp - test_formatted_size.cpp - test_formatter.cpp - test_iterator.cpp - test_print.cpp - test_ranges.cpp - test_reader.cpp - test_result.cpp + # detail/test_bignum.cpp + # detail/test_bitset.cpp + # detail/test_conversion.cpp + # detail/test_ct_vector.cpp + # detail/test_decode.cpp + # detail/test_dragon.cpp + # detail/test_utf.cpp + # test_buffer.cpp + # test_dynamic_format_spec.cpp + # test_format.cpp + # test_format_api.cpp + # test_format_as.cpp + # test_format_emio_vs_fmt.cpp + # test_format_string.cpp + # test_format_to_api.cpp + # test_format_could_fail_api.cpp + # test_format_to_n_api.cpp + # test_formatted_size.cpp + # test_formatter.cpp + # test_iterator.cpp + # test_print.cpp + # test_ranges.cpp + # test_reader.cpp + # test_result.cpp test_scan.cpp - test_std.cpp - test_writer.cpp + # test_std.cpp + # test_writer.cpp ) target_link_libraries(emio_test diff --git a/test/unit_test/test_scan.cpp b/test/unit_test/test_scan.cpp index e88e3c8..4767904 100644 --- a/test/unit_test/test_scan.cpp +++ b/test/unit_test/test_scan.cpp @@ -6,594 +6,635 @@ #include "integer_ranges.hpp" -namespace { - -template -bool validate_scan_string(std::string_view str) { - return emio::detail::scan::scan_trait::validate_string(str); -} - -} // namespace - -TEST_CASE("scan API", "[scan]") { - SECTION("no args") { - CHECK(emio::scan("abc{", "abc{{")); - } - SECTION("normal") { - unsigned int a = 0; - int b = 0; - char c; - REQUIRE(emio::scan("1,-2!", "{},{}{}", a, b, c)); - CHECK(a == 1); - CHECK(b == -2); - CHECK(c == '!'); - } - SECTION("runtime string") { - unsigned int a = 0; - int b = 0; - char c; - - REQUIRE(emio::scan("1,-2!", emio::runtime("{},{}{}"), a, b, c)); - CHECK(a == 1); - CHECK(b == -2); - CHECK(c == '!'); - } - SECTION("compile-time") { - constexpr bool success = [] { - unsigned int a = 0; - int b = 0; - char c; - - emio::result res = emio::scan("1,-2!", emio::runtime("{},{}{}"), a, b, c); - return res && (a == 1) && (b == -2) && (c == '!'); - }(); - STATIC_CHECK(success); - } -} - -TEST_CASE("vscan API", "[scan]") { - SECTION("no args") { - CHECK(emio::vscan("abc{", emio::make_scan_args("abc{{"))); - } - SECTION("normal") { - unsigned int a = 0; - int b = 0; - char c; - - REQUIRE(emio::vscan("1,-2!", emio::make_scan_args("{},{}{}", a, b, c))); - CHECK(a == 1); - CHECK(b == -2); - CHECK(c == '!'); - } - SECTION("runtime string") { - unsigned int a = 0; - int b = 0; - char c; - - REQUIRE(emio::vscan("1,-2!", emio::make_scan_args(emio::runtime("{},{}{}"), a, b, c))); - CHECK(a == 1); - CHECK(b == -2); - CHECK(c == '!'); - } -} - -TEST_CASE("scan_from API", "[scan]") { - SECTION("no args and no escapes") { - emio::reader rdr("abc"); - CHECK(emio::scan_from(rdr, "abc")); - CHECK(rdr.eof()); - } - SECTION("no args and escapes") { - emio::reader rdr("abc{"); - CHECK(emio::scan_from(rdr, "abc{{")); - CHECK(rdr.eof()); - } - SECTION("normal") { - unsigned int a = 0; - int b = 0; - char c; - - emio::reader rdr("1,-2!rest"); - REQUIRE(emio::scan_from(rdr, "{},{}{}", a, b, c)); - CHECK(a == 1); - CHECK(b == -2); - CHECK(c == '!'); - CHECK(rdr.read_remaining() == "rest"); - } - SECTION("runtime string") { - unsigned int a = 0; - int b = 0; - char c; - - emio::reader rdr("1,-2!rest"); - REQUIRE(emio::scan_from(rdr, emio::runtime("{},{}{}"), a, b, c)); - CHECK(a == 1); - CHECK(b == -2); - CHECK(c == '!'); - CHECK(rdr.read_remaining() == "rest"); - } - SECTION("compile time") { - constexpr bool success = [] { - emio::reader rdr("abc"); - emio::result res = emio::scan_from(rdr, emio::runtime("abc")); - return res && rdr.eof(); - }(); - STATIC_CHECK(success); - - constexpr bool success2 = [] { - unsigned int a = 0; - int b = 0; - char c; - - emio::reader rdr("1,-2!rest"); - emio::result res = emio::scan_from(rdr, emio::runtime("{},{}{}"), a, b, c); - return res && (a == 1) && (b == -2) && (c == '!') && (rdr.read_remaining() == "rest"); - }(); - STATIC_CHECK(success2); - } -} - -TEST_CASE("vscan_from API", "[scan]") { - SECTION("normal") { - unsigned int a = 0; - int b = 0; - char c; - - emio::reader rdr("1,-2!rest"); - REQUIRE(emio::vscan_from(rdr, emio::make_scan_args("{},{}{}", a, b, c))); - CHECK(a == 1); - CHECK(b == -2); - CHECK(c == '!'); - CHECK(rdr.read_remaining() == "rest"); - } - SECTION("runtime string") { - unsigned int a = 0; - int b = 0; - char c; - - emio::reader rdr("1,-2!rest"); - REQUIRE(emio::vscan_from(rdr, emio::make_scan_args(emio::runtime("{},{}{}"), a, b, c))); - CHECK(a == 1); - CHECK(b == -2); - CHECK(c == '!'); - CHECK(rdr.read_remaining() == "rest"); - } -} - -TEST_CASE("format scan string", "[scan]") { - SECTION("compile-time validation") { - emio::format_scan_string str{"{}"}; - CHECK(str.get() == "{}"); - } - SECTION("runtime validation") { - emio::format_scan_string str{emio::runtime("{}")}; - CHECK(str.get() == "{}"); - } - SECTION("failed runtime validation") { - emio::format_scan_string str{emio::runtime("{")}; - CHECK(str.get() == emio::err::invalid_format); - } -} - -TEST_CASE("incomplete scan", "[scan]") { - int a = 0; - int b = 0; - - SECTION("without reader") { - CHECK(!emio::scan("1,-2rest", "{},{}", a, b)); - } - SECTION("vscan") { - CHECK(!emio::vscan("1,-2rest", emio::make_scan_args("{},{}", a, b))); - } - SECTION("with reader") { - emio::reader rdr("1,-2rest"); - CHECK(emio::scan_from(rdr, "{},{}", a, b)); - CHECK(rdr.read_remaining() == "rest"); - } -} - -TEST_CASE("scan_char", "[scan]") { - char c; - REQUIRE(emio::scan("o", "{}", c)); - CHECK(c == 'o'); - - REQUIRE(emio::scan("k", "{:c}", c)); - CHECK(c == 'k'); - - REQUIRE(emio::scan("f", "{:1}", c)); - CHECK(c == 'f'); - - REQUIRE(emio::scan("g", "{:1c}", c)); - CHECK(c == 'g'); - - CHECK(validate_scan_string("{:c}")); - CHECK(!validate_scan_string("{:0}")); - CHECK(!validate_scan_string("{:2}")); - CHECK(!validate_scan_string("{:d}")); - CHECK(!validate_scan_string("{:#}")); -} - -TEST_CASE("detect base", "[scan]") { - int val{}; - - SECTION("binary") { - REQUIRE(emio::scan("0b1011", "{:#}", val)); - CHECK(val == 0b1011); - - REQUIRE(emio::scan("0B101", "{:#}", val)); - CHECK(val == 0b101); - - REQUIRE(emio::scan("+0b1010", "{:#}", val)); - CHECK(val == +0b1010); - - REQUIRE(emio::scan("-0b111", "{:#}", val)); - CHECK(val == -0b111); - } - SECTION("octal") { - REQUIRE(emio::scan("0175", "{:#}", val)); - CHECK(val == 0175); - - REQUIRE(emio::scan("+054", "{:#}", val)); - CHECK(val == +054); - - REQUIRE(emio::scan("-0023", "{:#}", val)); - CHECK(val == -023); - } - SECTION("decimal") { - REQUIRE(emio::scan("-0", "{:#}", val)); - CHECK(val == 0); - - REQUIRE(emio::scan("0", "{:#}", val)); - CHECK(val == 0); - - REQUIRE(emio::scan("1", "{:#}", val)); - CHECK(val == 1); - - REQUIRE(emio::scan("+1", "{:#}", val)); - CHECK(val == +1); - - REQUIRE(emio::scan("-1", "{:#}", val)); - CHECK(val == -1); - } - SECTION("hex") { - REQUIRE(emio::scan("0x58", "{:#}", val)); - CHECK(val == 0x58); - - REQUIRE(emio::scan("0XAe", "{:#}", val)); - CHECK(val == 0xAE); - - REQUIRE(emio::scan("+0x5", "{:#}", val)); - CHECK(val == +0x5); - - REQUIRE(emio::scan("-0x1", "{:#}", val)); - CHECK(val == -0x1); - } - SECTION("invalid") { - CHECK(emio::scan("-", "{:#}", val) == emio::err::eof); - CHECK(emio::scan("--1", "{:#}", val) == emio::err::invalid_data); - CHECK(emio::scan("a", "{:#}", val) == emio::err::invalid_data); - CHECK(emio::scan("0xx", "{:#}", val) == emio::err::invalid_data); - CHECK(emio::scan("0x+1", "{:#}", val) == emio::err::invalid_data); - CHECK(emio::scan("0x-1", "{:#}", val) == emio::err::invalid_data); - } - SECTION("unsigned") { - unsigned int uval{}; - CHECK(emio::scan("+5", "{:#}", uval)); - CHECK(uval == 5); - - CHECK(emio::scan("-1", "{:#}", uval) == emio::err::out_of_range); - CHECK(emio::scan("-0x5", "{:#}", uval) == emio::err::out_of_range); - } - - CHECK(validate_scan_string("{:#x}")); - CHECK(!validate_scan_string("{:d#}")); - CHECK(!validate_scan_string("{:f}")); - CHECK(!validate_scan_string("{:.5}")); -} - -TEST_CASE("integral with width", "[scan]") { - int val{}; - int val2{}; - - REQUIRE(emio::scan("1524", "{:2}{:2}", val, val2)); - CHECK(val == 15); - CHECK(val2 == 24); - - REQUIRE(emio::scan("+0x5", "{:#4}", val)); - CHECK(val == 5); - - REQUIRE(emio::scan("+0x5", "{:#2}x{}", val, val2)); - CHECK(val == 0); - CHECK(val2 == 5); - - REQUIRE(emio::scan("12ab", "{:4}", val) == emio::err::invalid_data); - REQUIRE(emio::scan("+0x5", "{:#1}0x5", val) == emio::err::eof); - REQUIRE(emio::scan("+0x5", "{:#6}", val) == emio::err::eof); - REQUIRE(emio::scan("+0x5", "{:#3}5", val) == emio::err::eof); - - CHECK(!validate_scan_string("{:+5}")); - CHECK(!validate_scan_string("{:-5}")); -} - -TEST_CASE("scan integral of different types", "[scan]") { - const auto range_check = [](std::type_identity /*type*/, const auto& lower_input, - const auto& upper_input) { - if constexpr (!std::is_same_v) { - T val{}; - REQUIRE(emio::scan(std::get<0>(lower_input), "{}", val)); - CHECK(val == std::numeric_limits::min() + 1); - REQUIRE(emio::scan(std::get<1>(lower_input), "{}", val)); - CHECK(val == std::numeric_limits::min()); - REQUIRE(emio::scan(std::get<2>(lower_input), "{}", val) == emio::err::out_of_range); - REQUIRE(emio::scan(std::get<3>(lower_input), "{}", val) == emio::err::out_of_range); - - REQUIRE(emio::scan(std::get<0>(upper_input), "{}", val)); - CHECK(val == std::numeric_limits::max() - 1); - REQUIRE(emio::scan(std::get<1>(upper_input), "{}", val)); - CHECK(val == std::numeric_limits::max()); - REQUIRE(emio::scan(std::get<2>(upper_input), "{}", val) == emio::err::out_of_range); - REQUIRE(emio::scan(std::get<3>(upper_input), "{}", val) == emio::err::out_of_range); +// namespace { + +// template +// bool validate_scan_string(std::string_view str) { +// return emio::detail::scan::scan_trait::validate_string(str); +// } + +// } // namespace + +// TEST_CASE("scan API", "[scan]") { +// SECTION("no args") { +// CHECK(emio::scan("abc{", "abc{{")); +// } +// SECTION("normal") { +// unsigned int a = 0; +// int b = 0; +// char c; +// REQUIRE(emio::scan("1,-2!", "{},{}{}", a, b, c)); +// CHECK(a == 1); +// CHECK(b == -2); +// CHECK(c == '!'); +// } +// SECTION("runtime string") { +// unsigned int a = 0; +// int b = 0; +// char c; + +// REQUIRE(emio::scan("1,-2!", emio::runtime("{},{}{}"), a, b, c)); +// CHECK(a == 1); +// CHECK(b == -2); +// CHECK(c == '!'); +// } +// SECTION("compile-time") { +// constexpr bool success = [] { +// unsigned int a = 0; +// int b = 0; +// char c; + +// emio::result res = emio::scan("1,-2!", emio::runtime("{},{}{}"), a, b, c); +// return res && (a == 1) && (b == -2) && (c == '!'); +// }(); +// STATIC_CHECK(success); +// } +// } + +// TEST_CASE("vscan API", "[scan]") { +// SECTION("no args") { +// CHECK(emio::vscan("abc{", emio::make_scan_args("abc{{"))); +// } +// SECTION("normal") { +// unsigned int a = 0; +// int b = 0; +// char c; + +// REQUIRE(emio::vscan("1,-2!", emio::make_scan_args("{},{}{}", a, b, c))); +// CHECK(a == 1); +// CHECK(b == -2); +// CHECK(c == '!'); +// } +// SECTION("runtime string") { +// unsigned int a = 0; +// int b = 0; +// char c; + +// REQUIRE(emio::vscan("1,-2!", emio::make_scan_args(emio::runtime("{},{}{}"), a, b, c))); +// CHECK(a == 1); +// CHECK(b == -2); +// CHECK(c == '!'); +// } +// } + +// TEST_CASE("scan_from API", "[scan]") { +// SECTION("no args and no escapes") { +// emio::reader rdr("abc"); +// CHECK(emio::scan_from(rdr, "abc")); +// CHECK(rdr.eof()); +// } +// SECTION("no args and escapes") { +// emio::reader rdr("abc{"); +// CHECK(emio::scan_from(rdr, "abc{{")); +// CHECK(rdr.eof()); +// } +// SECTION("normal") { +// unsigned int a = 0; +// int b = 0; +// char c; + +// emio::reader rdr("1,-2!rest"); +// REQUIRE(emio::scan_from(rdr, "{},{}{}", a, b, c)); +// CHECK(a == 1); +// CHECK(b == -2); +// CHECK(c == '!'); +// CHECK(rdr.read_remaining() == "rest"); +// } +// SECTION("runtime string") { +// unsigned int a = 0; +// int b = 0; +// char c; + +// emio::reader rdr("1,-2!rest"); +// REQUIRE(emio::scan_from(rdr, emio::runtime("{},{}{}"), a, b, c)); +// CHECK(a == 1); +// CHECK(b == -2); +// CHECK(c == '!'); +// CHECK(rdr.read_remaining() == "rest"); +// } +// SECTION("compile time") { +// constexpr bool success = [] { +// emio::reader rdr("abc"); +// emio::result res = emio::scan_from(rdr, emio::runtime("abc")); +// return res && rdr.eof(); +// }(); +// STATIC_CHECK(success); + +// constexpr bool success2 = [] { +// unsigned int a = 0; +// int b = 0; +// char c; + +// emio::reader rdr("1,-2!rest"); +// emio::result res = emio::scan_from(rdr, emio::runtime("{},{}{}"), a, b, c); +// return res && (a == 1) && (b == -2) && (c == '!') && (rdr.read_remaining() == "rest"); +// }(); +// STATIC_CHECK(success2); +// } +// } + +// TEST_CASE("vscan_from API", "[scan]") { +// SECTION("normal") { +// unsigned int a = 0; +// int b = 0; +// char c; + +// emio::reader rdr("1,-2!rest"); +// REQUIRE(emio::vscan_from(rdr, emio::make_scan_args("{},{}{}", a, b, c))); +// CHECK(a == 1); +// CHECK(b == -2); +// CHECK(c == '!'); +// CHECK(rdr.read_remaining() == "rest"); +// } +// SECTION("runtime string") { +// unsigned int a = 0; +// int b = 0; +// char c; + +// emio::reader rdr("1,-2!rest"); +// REQUIRE(emio::vscan_from(rdr, emio::make_scan_args(emio::runtime("{},{}{}"), a, b, c))); +// CHECK(a == 1); +// CHECK(b == -2); +// CHECK(c == '!'); +// CHECK(rdr.read_remaining() == "rest"); +// } +// } + +// TEST_CASE("format scan string", "[scan]") { +// SECTION("compile-time validation") { +// emio::format_scan_string str{"{}"}; +// CHECK(str.get() == "{}"); +// } +// SECTION("runtime validation") { +// emio::format_scan_string str{emio::runtime("{}")}; +// CHECK(str.get() == "{}"); +// } +// SECTION("failed runtime validation") { +// emio::format_scan_string str{emio::runtime("{")}; +// CHECK(str.get() == emio::err::invalid_format); +// } +// } + +// TEST_CASE("incomplete scan", "[scan]") { +// int a = 0; +// int b = 0; + +// SECTION("without reader") { +// CHECK(!emio::scan("1,-2rest", "{},{}", a, b)); +// } +// SECTION("vscan") { +// CHECK(!emio::vscan("1,-2rest", emio::make_scan_args("{},{}", a, b))); +// } +// SECTION("with reader") { +// emio::reader rdr("1,-2rest"); +// CHECK(emio::scan_from(rdr, "{},{}", a, b)); +// CHECK(rdr.read_remaining() == "rest"); +// } +// } + +// TEST_CASE("scan_char", "[scan]") { +// char c; +// REQUIRE(emio::scan("o", "{}", c)); +// CHECK(c == 'o'); + +// REQUIRE(emio::scan("k", "{:c}", c)); +// CHECK(c == 'k'); + +// REQUIRE(emio::scan("f", "{:1}", c)); +// CHECK(c == 'f'); + +// REQUIRE(emio::scan("g", "{:1c}", c)); +// CHECK(c == 'g'); + +// CHECK(validate_scan_string("{:c}")); +// CHECK(!validate_scan_string("{:0}")); +// CHECK(!validate_scan_string("{:2}")); +// CHECK(!validate_scan_string("{:d}")); +// CHECK(!validate_scan_string("{:#}")); +// } + +// TEST_CASE("detect base", "[scan]") { +// int val{}; + +// SECTION("binary") { +// REQUIRE(emio::scan("0b1011", "{:#}", val)); +// CHECK(val == 0b1011); + +// REQUIRE(emio::scan("0B101", "{:#}", val)); +// CHECK(val == 0b101); + +// REQUIRE(emio::scan("+0b1010", "{:#}", val)); +// CHECK(val == +0b1010); + +// REQUIRE(emio::scan("-0b111", "{:#}", val)); +// CHECK(val == -0b111); +// } +// SECTION("octal") { +// REQUIRE(emio::scan("0175", "{:#}", val)); +// CHECK(val == 0175); + +// REQUIRE(emio::scan("+054", "{:#}", val)); +// CHECK(val == +054); + +// REQUIRE(emio::scan("-0023", "{:#}", val)); +// CHECK(val == -023); +// } +// SECTION("decimal") { +// REQUIRE(emio::scan("-0", "{:#}", val)); +// CHECK(val == 0); + +// REQUIRE(emio::scan("0", "{:#}", val)); +// CHECK(val == 0); + +// REQUIRE(emio::scan("1", "{:#}", val)); +// CHECK(val == 1); + +// REQUIRE(emio::scan("+1", "{:#}", val)); +// CHECK(val == +1); + +// REQUIRE(emio::scan("-1", "{:#}", val)); +// CHECK(val == -1); +// } +// SECTION("hex") { +// REQUIRE(emio::scan("0x58", "{:#}", val)); +// CHECK(val == 0x58); + +// REQUIRE(emio::scan("0XAe", "{:#}", val)); +// CHECK(val == 0xAE); + +// REQUIRE(emio::scan("+0x5", "{:#}", val)); +// CHECK(val == +0x5); + +// REQUIRE(emio::scan("-0x1", "{:#}", val)); +// CHECK(val == -0x1); +// } +// SECTION("invalid") { +// CHECK(emio::scan("-", "{:#}", val) == emio::err::eof); +// CHECK(emio::scan("--1", "{:#}", val) == emio::err::invalid_data); +// CHECK(emio::scan("a", "{:#}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0xx", "{:#}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0x+1", "{:#}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0x-1", "{:#}", val) == emio::err::invalid_data); +// } +// SECTION("unsigned") { +// unsigned int uval{}; +// CHECK(emio::scan("+5", "{:#}", uval)); +// CHECK(uval == 5); + +// CHECK(emio::scan("-1", "{:#}", uval) == emio::err::out_of_range); +// CHECK(emio::scan("-0x5", "{:#}", uval) == emio::err::out_of_range); +// } + +// CHECK(validate_scan_string("{:#x}")); +// CHECK(!validate_scan_string("{:d#}")); +// CHECK(!validate_scan_string("{:f}")); +// CHECK(!validate_scan_string("{:.5}")); +// } + +// TEST_CASE("integral with width", "[scan]") { +// int val{}; +// int val2{}; + +// REQUIRE(emio::scan("1524", "{:2}{:2}", val, val2)); +// CHECK(val == 15); +// CHECK(val2 == 24); + +// REQUIRE(emio::scan("+0x5", "{:#4}", val)); +// CHECK(val == 5); + +// REQUIRE(emio::scan("+0x5", "{:#2}x{}", val, val2)); +// CHECK(val == 0); +// CHECK(val2 == 5); + +// REQUIRE(emio::scan("12ab", "{:4}", val) == emio::err::invalid_data); +// REQUIRE(emio::scan("+0x5", "{:#1}0x5", val) == emio::err::eof); +// REQUIRE(emio::scan("+0x5", "{:#6}", val) == emio::err::eof); +// REQUIRE(emio::scan("+0x5", "{:#3}5", val) == emio::err::eof); + +// CHECK(!validate_scan_string("{:+5}")); +// CHECK(!validate_scan_string("{:-5}")); +// } + +// TEST_CASE("scan integral of different types", "[scan]") { +// const auto range_check = [](std::type_identity /*type*/, const auto& lower_input, +// const auto& upper_input) { +// if constexpr (!std::is_same_v) { +// T val{}; +// REQUIRE(emio::scan(std::get<0>(lower_input), "{}", val)); +// CHECK(val == std::numeric_limits::min() + 1); +// REQUIRE(emio::scan(std::get<1>(lower_input), "{}", val)); +// CHECK(val == std::numeric_limits::min()); +// REQUIRE(emio::scan(std::get<2>(lower_input), "{}", val) == emio::err::out_of_range); +// REQUIRE(emio::scan(std::get<3>(lower_input), "{}", val) == emio::err::out_of_range); + +// REQUIRE(emio::scan(std::get<0>(upper_input), "{}", val)); +// CHECK(val == std::numeric_limits::max() - 1); +// REQUIRE(emio::scan(std::get<1>(upper_input), "{}", val)); +// CHECK(val == std::numeric_limits::max()); +// REQUIRE(emio::scan(std::get<2>(upper_input), "{}", val) == emio::err::out_of_range); +// REQUIRE(emio::scan(std::get<3>(upper_input), "{}", val) == emio::err::out_of_range); +// } +// }; +// emio::test::apply_integer_ranges(range_check); +// } + +// TEST_CASE("scan_binary", "[scan]") { +// int val{}; +// SECTION("no prefix") { +// REQUIRE(emio::scan("1011", "{:b}", val)); +// CHECK(val == 0b1011); + +// REQUIRE(emio::scan("+1001", "{:b}", val)); +// CHECK(val == +0b1001); + +// REQUIRE(emio::scan("-11", "{:b}", val)); +// CHECK(val == -0b11); +// } +// SECTION("invalid number") { +// CHECK_FALSE(emio::scan("2", "{:b}", val)); +// } +// SECTION("with prefix") { +// REQUIRE(emio::scan("0b10", "{:#b}", val)); +// CHECK(val == 0b10); + +// REQUIRE(emio::scan("0B110", "{:#b}", val)); +// CHECK(val == 0b110); + +// REQUIRE(emio::scan("+0b101", "{:#b}", val)); +// CHECK(val == +0b101); + +// REQUIRE(emio::scan("-0b101", "{:#b}", val)); +// CHECK(val == -0b101); +// } +// SECTION("wrong prefix") { +// CHECK(emio::scan("0", "{:#b}", val) == emio::err::eof); +// CHECK(emio::scan("0o", "{:#b}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0x1", "{:#b}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0b+1", "{:#b}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0b-1", "{:#b}", val) == emio::err::invalid_data); +// } +// } + +// TEST_CASE("scan_octal", "[scan]") { +// int val{}; +// SECTION("no prefix") { +// REQUIRE(emio::scan("75", "{:o}", val)); +// CHECK(val == 075); + +// REQUIRE(emio::scan("+12", "{:o}", val)); +// CHECK(val == +012); + +// REQUIRE(emio::scan("-62", "{:o}", val)); +// CHECK(val == -062); +// } +// SECTION("invalid number") { +// CHECK(emio::scan("8", "{:o}", val) == emio::err::invalid_data); +// } +// SECTION("with prefix") { +// REQUIRE(emio::scan("00", "{:#o}", val)); +// CHECK(val == 00); + +// REQUIRE(emio::scan("050", "{:#o}", val)); +// CHECK(val == 050); + +// REQUIRE(emio::scan("+050", "{:#o}", val)); +// CHECK(val == +050); + +// REQUIRE(emio::scan("-050", "{:#o}", val)); +// CHECK(val == -050); +// } +// SECTION("wrong prefix") { +// CHECK(emio::scan("0", "{:#o}", val) == emio::err::eof); +// CHECK(emio::scan("0o", "{:#o}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0+0", "{:#o}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0-0", "{:#o}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0b3", "{:#o}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0x3", "{:#o}", val) == emio::err::invalid_data); +// } +// } + +// TEST_CASE("scan_decimal", "[scan]") { +// int val{}; +// SECTION("no prefix") { +// REQUIRE(emio::scan("49", "{}", val)); +// CHECK(val == 49); + +// REQUIRE(emio::scan("-87", "{}", val)); +// CHECK(val == -87); + +// REQUIRE(emio::scan("59849", "{:d}", val)); +// CHECK(val == 59849); + +// REQUIRE(emio::scan("+47891", "{:d}", val)); +// CHECK(val == +47891); + +// REQUIRE(emio::scan("-259", "{:d}", val)); +// CHECK(val == -259); +// } +// SECTION("invalid number") { +// CHECK(emio::scan("a", "{:d}", val) == emio::err::invalid_data); +// } +// SECTION("with prefix") { +// REQUIRE(emio::scan("42", "{:#d}", val)); +// CHECK(val == 42); + +// REQUIRE(emio::scan("240", "{:#d}", val)); +// CHECK(val == 240); + +// REQUIRE(emio::scan("+42", "{:#d}", val)); +// CHECK(val == +42); + +// REQUIRE(emio::scan("-42", "{:#d}", val)); +// CHECK(val == -42); +// } +// SECTION("edge cases") { +// val = -1; +// SECTION("test 1") { +// emio::reader rdr{"0x5"}; +// REQUIRE(emio::scan_from(rdr, "{}", val)); +// CHECK(val == 0); +// CHECK(rdr.read_remaining() == "x5"); +// } + +// SECTION("test 2") { +// emio::reader rdr{"0b3"}; +// REQUIRE(emio::scan_from(rdr, "{:#d}", val)); +// CHECK(val == 0); +// CHECK(rdr.read_remaining() == "b3"); +// } + +// SECTION("test 3") { +// REQUIRE(emio::scan("0", "{:#d}", val)); +// CHECK(val == 0); +// } +// } +// SECTION("overflows") { +// SECTION("signed") { +// int8_t val8{}; +// CHECK(emio::scan("127", "{}", val8)); +// CHECK(val8 == 127); + +// CHECK(emio::scan("128", "{}", val8) == emio::err::out_of_range); + +// REQUIRE(emio::scan("-128", "{}", val8)); +// CHECK(val8 == -128); + +// CHECK(emio::scan("-129", "{}", val8) == emio::err::out_of_range); +// } +// SECTION("unsigned") { +// uint8_t uval8{}; +// REQUIRE(emio::scan("255", "{}", uval8)); +// CHECK(uval8 == 255); + +// CHECK(emio::scan("256", "{}", uval8) == emio::err::out_of_range); +// } +// } +// } + +// TEST_CASE("scan_hex", "[scan]") { +// int val{}; +// SECTION("no prefix") { +// REQUIRE(emio::scan("Ad", "{:x}", val)); +// CHECK(val == 0xAD); + +// REQUIRE(emio::scan("+fe", "{:x}", val)); +// CHECK(val == +0xfe); + +// REQUIRE(emio::scan("-1d", "{:x}", val)); +// CHECK(val == -0x1d); +// } +// SECTION("invalid number") { +// CHECK(emio::scan("g", "{:x}", val) == emio::err::invalid_data); +// } +// SECTION("with prefix") { +// REQUIRE(emio::scan("0xdf", "{:#x}", val)); +// CHECK(val == 0xdf); + +// REQUIRE(emio::scan("0X1e", "{:#x}", val)); +// CHECK(val == 0x1e); + +// REQUIRE(emio::scan("+0x1e", "{:#x}", val)); +// CHECK(val == +0x1e); + +// REQUIRE(emio::scan("-0x1e", "{:#x}", val)); +// CHECK(val == -0x1e); +// } +// SECTION("wrong prefix") { +// CHECK(emio::scan("da", "{:#x}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0b3", "{:#x}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0o3", "{:#x}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0x+3", "{:#x}", val) == emio::err::invalid_data); +// CHECK(emio::scan("0x-3", "{:#x}", val) == emio::err::invalid_data); +// } +// } + +// TEST_CASE("scan_string", "[scan]") { +// std::string s; +// SECTION("until eof") { +// REQUIRE(emio::scan("abc", "{}", s)); +// CHECK(s == "abc"); + +// REQUIRE(emio::scan("abc", "a{}", s)); +// CHECK(s == "bc"); + +// REQUIRE(emio::scan("abc", "ab{}", s)); +// CHECK(s == "c"); + +// REQUIRE(emio::scan("abc", "abc{}", s)); +// CHECK(s.empty()); +// } +// SECTION("until given size") { +// REQUIRE(emio::scan("abc", "{:3}", s)); +// CHECK(s == "abc"); + +// REQUIRE(emio::scan("aaa", "{:2}a", s)); +// CHECK(s == "aa"); + +// REQUIRE(emio::scan("abc", "{:4}", s) == emio::err::eof); +// REQUIRE(emio::scan("abc", "{:3}c", s) == emio::err::eof); +// } +// SECTION("until next") { +// std::string s2; +// REQUIRE(emio::scan("abc", "{}b{}", s, s2)); +// CHECK(s == "a"); +// CHECK(s2 == "c"); + +// REQUIRE(emio::scan("abc", "{}abc", s)); +// CHECK(s.empty()); + +// REQUIRE(emio::scan("abc", "{}{}c", s, s2)); +// CHECK(s.empty()); +// CHECK(s2 == "ab"); + +// REQUIRE(emio::scan("abc", "{:2}{}c", s, s2)); +// CHECK(s == "ab"); +// CHECK(s2.empty()); + +// REQUIRE(emio::scan("abc", "{:1}{}c", s, s2)); +// CHECK(s == "a"); +// CHECK(s2 == "b"); +// } +// SECTION("complex") { +// std::string s2; +// REQUIRE(emio::scan("abc{", "{}{{", s)); +// CHECK(s == "abc"); + +// REQUIRE(emio::scan("abc}", "{}}}", s)); +// CHECK(s == "abc"); + +// REQUIRE(emio::scan("abc{}def}x", "{}}}x", s)); +// CHECK(s == "abc{}def"); + +// REQUIRE(emio::scan("abc{}def}x12", "{}}}x{}", s, s2)); +// CHECK(s == "abc{}def"); +// CHECK(s2 == "12"); + +// REQUIRE(emio::scan("abc{x", "{}{{y", s) == emio::err::invalid_data); +// } + +// CHECK(validate_scan_string("{:s}")); +// CHECK(validate_scan_string("{:s}")); +// CHECK(!validate_scan_string("{:#}")); +// CHECK(!validate_scan_string("{:d}")); +// } + +#include +#include +#include +#include +#include + +TEST_CASE("double") { + // #define double float + // double d{}; + // // // + // emio::scan("-4.99039e+09", "{}", d).value(); + // REQUIRE(d == -4.99039e+09); + // // + // // INSERT_YOUR_CODE + // // + // return; + + SECTION("random doubles") { + std::mt19937_64 rng{}; // fixed seed for reproducibility + + std::uniform_real_distribution dist(-1e10, 1e10); + + // for (int32_t i = 0; i < 1e5; ++i) { + while (true) { + double original = dist(rng); + + // Convert to string with maximum precision + auto s = emio::format("{}", original); + double parsed = 0.0; + + INFO(original); + + REQUIRE(emio::scan(s, "{}", parsed)); + REQUIRE(parsed == original); } - }; - emio::test::apply_integer_ranges(range_check); -} - -TEST_CASE("scan_binary", "[scan]") { - int val{}; - SECTION("no prefix") { - REQUIRE(emio::scan("1011", "{:b}", val)); - CHECK(val == 0b1011); - - REQUIRE(emio::scan("+1001", "{:b}", val)); - CHECK(val == +0b1001); - - REQUIRE(emio::scan("-11", "{:b}", val)); - CHECK(val == -0b11); - } - SECTION("invalid number") { - CHECK_FALSE(emio::scan("2", "{:b}", val)); - } - SECTION("with prefix") { - REQUIRE(emio::scan("0b10", "{:#b}", val)); - CHECK(val == 0b10); - - REQUIRE(emio::scan("0B110", "{:#b}", val)); - CHECK(val == 0b110); - - REQUIRE(emio::scan("+0b101", "{:#b}", val)); - CHECK(val == +0b101); - - REQUIRE(emio::scan("-0b101", "{:#b}", val)); - CHECK(val == -0b101); - } - SECTION("wrong prefix") { - CHECK(emio::scan("0", "{:#b}", val) == emio::err::eof); - CHECK(emio::scan("0o", "{:#b}", val) == emio::err::invalid_data); - CHECK(emio::scan("0x1", "{:#b}", val) == emio::err::invalid_data); - CHECK(emio::scan("0b+1", "{:#b}", val) == emio::err::invalid_data); - CHECK(emio::scan("0b-1", "{:#b}", val) == emio::err::invalid_data); - } -} - -TEST_CASE("scan_octal", "[scan]") { - int val{}; - SECTION("no prefix") { - REQUIRE(emio::scan("75", "{:o}", val)); - CHECK(val == 075); - - REQUIRE(emio::scan("+12", "{:o}", val)); - CHECK(val == +012); - - REQUIRE(emio::scan("-62", "{:o}", val)); - CHECK(val == -062); - } - SECTION("invalid number") { - CHECK(emio::scan("8", "{:o}", val) == emio::err::invalid_data); - } - SECTION("with prefix") { - REQUIRE(emio::scan("00", "{:#o}", val)); - CHECK(val == 00); - - REQUIRE(emio::scan("050", "{:#o}", val)); - CHECK(val == 050); - - REQUIRE(emio::scan("+050", "{:#o}", val)); - CHECK(val == +050); - - REQUIRE(emio::scan("-050", "{:#o}", val)); - CHECK(val == -050); - } - SECTION("wrong prefix") { - CHECK(emio::scan("0", "{:#o}", val) == emio::err::eof); - CHECK(emio::scan("0o", "{:#o}", val) == emio::err::invalid_data); - CHECK(emio::scan("0+0", "{:#o}", val) == emio::err::invalid_data); - CHECK(emio::scan("0-0", "{:#o}", val) == emio::err::invalid_data); - CHECK(emio::scan("0b3", "{:#o}", val) == emio::err::invalid_data); - CHECK(emio::scan("0x3", "{:#o}", val) == emio::err::invalid_data); - } -} - -TEST_CASE("scan_decimal", "[scan]") { - int val{}; - SECTION("no prefix") { - REQUIRE(emio::scan("49", "{}", val)); - CHECK(val == 49); - - REQUIRE(emio::scan("-87", "{}", val)); - CHECK(val == -87); - - REQUIRE(emio::scan("59849", "{:d}", val)); - CHECK(val == 59849); - - REQUIRE(emio::scan("+47891", "{:d}", val)); - CHECK(val == +47891); - - REQUIRE(emio::scan("-259", "{:d}", val)); - CHECK(val == -259); - } - SECTION("invalid number") { - CHECK(emio::scan("a", "{:d}", val) == emio::err::invalid_data); - } - SECTION("with prefix") { - REQUIRE(emio::scan("42", "{:#d}", val)); - CHECK(val == 42); - - REQUIRE(emio::scan("240", "{:#d}", val)); - CHECK(val == 240); - - REQUIRE(emio::scan("+42", "{:#d}", val)); - CHECK(val == +42); - - REQUIRE(emio::scan("-42", "{:#d}", val)); - CHECK(val == -42); - } - SECTION("edge cases") { - val = -1; - SECTION("test 1") { - emio::reader rdr{"0x5"}; - REQUIRE(emio::scan_from(rdr, "{}", val)); - CHECK(val == 0); - CHECK(rdr.read_remaining() == "x5"); - } - - SECTION("test 2") { - emio::reader rdr{"0b3"}; - REQUIRE(emio::scan_from(rdr, "{:#d}", val)); - CHECK(val == 0); - CHECK(rdr.read_remaining() == "b3"); - } - - SECTION("test 3") { - REQUIRE(emio::scan("0", "{:#d}", val)); - CHECK(val == 0); - } - } - SECTION("overflows") { - SECTION("signed") { - int8_t val8{}; - CHECK(emio::scan("127", "{}", val8)); - CHECK(val8 == 127); - - CHECK(emio::scan("128", "{}", val8) == emio::err::out_of_range); - - REQUIRE(emio::scan("-128", "{}", val8)); - CHECK(val8 == -128); - - CHECK(emio::scan("-129", "{}", val8) == emio::err::out_of_range); - } - SECTION("unsigned") { - uint8_t uval8{}; - REQUIRE(emio::scan("255", "{}", uval8)); - CHECK(uval8 == 255); - - CHECK(emio::scan("256", "{}", uval8) == emio::err::out_of_range); - } - } -} - -TEST_CASE("scan_hex", "[scan]") { - int val{}; - SECTION("no prefix") { - REQUIRE(emio::scan("Ad", "{:x}", val)); - CHECK(val == 0xAD); - - REQUIRE(emio::scan("+fe", "{:x}", val)); - CHECK(val == +0xfe); - - REQUIRE(emio::scan("-1d", "{:x}", val)); - CHECK(val == -0x1d); - } - SECTION("invalid number") { - CHECK(emio::scan("g", "{:x}", val) == emio::err::invalid_data); - } - SECTION("with prefix") { - REQUIRE(emio::scan("0xdf", "{:#x}", val)); - CHECK(val == 0xdf); - - REQUIRE(emio::scan("0X1e", "{:#x}", val)); - CHECK(val == 0x1e); - - REQUIRE(emio::scan("+0x1e", "{:#x}", val)); - CHECK(val == +0x1e); - - REQUIRE(emio::scan("-0x1e", "{:#x}", val)); - CHECK(val == -0x1e); - } - SECTION("wrong prefix") { - CHECK(emio::scan("da", "{:#x}", val) == emio::err::invalid_data); - CHECK(emio::scan("0b3", "{:#x}", val) == emio::err::invalid_data); - CHECK(emio::scan("0o3", "{:#x}", val) == emio::err::invalid_data); - CHECK(emio::scan("0x+3", "{:#x}", val) == emio::err::invalid_data); - CHECK(emio::scan("0x-3", "{:#x}", val) == emio::err::invalid_data); - } -} - -TEST_CASE("scan_string", "[scan]") { - std::string s; - SECTION("until eof") { - REQUIRE(emio::scan("abc", "{}", s)); - CHECK(s == "abc"); - - REQUIRE(emio::scan("abc", "a{}", s)); - CHECK(s == "bc"); - - REQUIRE(emio::scan("abc", "ab{}", s)); - CHECK(s == "c"); - - REQUIRE(emio::scan("abc", "abc{}", s)); - CHECK(s.empty()); - } - SECTION("until given size") { - REQUIRE(emio::scan("abc", "{:3}", s)); - CHECK(s == "abc"); - - REQUIRE(emio::scan("aaa", "{:2}a", s)); - CHECK(s == "aa"); - - REQUIRE(emio::scan("abc", "{:4}", s) == emio::err::eof); - REQUIRE(emio::scan("abc", "{:3}c", s) == emio::err::eof); - } - SECTION("until next") { - std::string s2; - REQUIRE(emio::scan("abc", "{}b{}", s, s2)); - CHECK(s == "a"); - CHECK(s2 == "c"); - - REQUIRE(emio::scan("abc", "{}abc", s)); - CHECK(s.empty()); - - REQUIRE(emio::scan("abc", "{}{}c", s, s2)); - CHECK(s.empty()); - CHECK(s2 == "ab"); - - REQUIRE(emio::scan("abc", "{:2}{}c", s, s2)); - CHECK(s == "ab"); - CHECK(s2.empty()); - - REQUIRE(emio::scan("abc", "{:1}{}c", s, s2)); - CHECK(s == "a"); - CHECK(s2 == "b"); - } - SECTION("complex") { - std::string s2; - REQUIRE(emio::scan("abc{", "{}{{", s)); - CHECK(s == "abc"); - - REQUIRE(emio::scan("abc}", "{}}}", s)); - CHECK(s == "abc"); - - REQUIRE(emio::scan("abc{}def}x", "{}}}x", s)); - CHECK(s == "abc{}def"); - - REQUIRE(emio::scan("abc{}def}x12", "{}}}x{}", s, s2)); - CHECK(s == "abc{}def"); - CHECK(s2 == "12"); - - REQUIRE(emio::scan("abc{x", "{}{{y", s) == emio::err::invalid_data); } - CHECK(validate_scan_string("{:s}")); - CHECK(validate_scan_string("{:s}")); - CHECK(!validate_scan_string("{:#}")); - CHECK(!validate_scan_string("{:d}")); + // REQUIRE(emio::scan("800", "{}", d)); + // CHECK(d == 1.23); }