From bcdad5b8762060c82a0b7840cb905e69ddb9a65e Mon Sep 17 00:00:00 2001 From: Steven Le Rouzic Date: Thu, 3 Jul 2025 18:37:18 +0200 Subject: Add numbers parsing --- asl/strings/parse_number.cpp | 162 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 162 insertions(+) create mode 100644 asl/strings/parse_number.cpp (limited to 'asl/strings/parse_number.cpp') diff --git a/asl/strings/parse_number.cpp b/asl/strings/parse_number.cpp new file mode 100644 index 0000000..3b6b689 --- /dev/null +++ b/asl/strings/parse_number.cpp @@ -0,0 +1,162 @@ +// Copyright 2025 Steven Le Rouzic +// +// SPDX-License-Identifier: BSD-3-Clause + +#include "asl/strings/parse_number.hpp" + +namespace asl +{ + +bool parse_float_impl(const char** begin, const char* end, float*); +bool parse_double_impl(const char** begin, const char* end, double*); + +} // namespace asl + +asl::status_or> asl::parse_float(asl::string_view sv) +{ + const auto* begin = sv.data(); + // NOLINTNEXTLINE(*-pointer-arithmetic) + const auto* end = begin + sv.size(); + if (float value{}; parse_float_impl(&begin, end, &value)) + { + return parse_number_result{ + .value = value, + .remaining = string_view{begin, end}, + }; + } + return invalid_argument_error(); +} + +asl::status_or> asl::parse_double(asl::string_view sv) +{ + const auto* begin = sv.data(); + // NOLINTNEXTLINE(*-pointer-arithmetic) + const auto* end = begin + sv.size(); + if (float value{}; parse_float_impl(&begin, end, &value)) + { + return parse_number_result{ + .value = value, + .remaining = string_view{begin, end}, + }; + } + return invalid_argument_error(); +} + +namespace +{ + +constexpr int8_t kBase16Table[256] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, +}; + +// @Todo Maybe monomorph this for common bases (2, 16, 10)? + +template +asl::status_or> parse_integer(asl::string_view sv, int base) +{ + ASL_ASSERT(base >= 2 && base <= 16); + + if (sv.is_empty()) { return asl::invalid_argument_error(); } + + T value = 0; + bool is_negative = false; + + if (asl::is_signed_integer && sv[0] == '-') + { + is_negative = true; + sv = sv.substr(1); + } + + isize_t cursor = 0; + while (cursor < sv.size()) + { + // NOLINTNEXTLINE(*-array-index) + int8_t digit = kBase16Table[static_cast(sv[cursor])]; + if (digit < 0 || digit >= base) { break; } + + if (__builtin_mul_overflow(value, static_cast(base), &value)) + { + return asl::invalid_argument_error("overflow"); + } + + if (asl::is_signed_integer && is_negative) + { + digit = static_cast(-digit); + } + + if (__builtin_add_overflow(value, static_cast(digit), &value)) + { + return asl::invalid_argument_error("overflow"); + } + + cursor += 1; + } + + if (cursor == 0) + { + return asl::invalid_argument_error(); + } + + return asl::parse_number_result{ + .value = value, + .remaining = sv.substr(cursor), + }; +} + +} // anonymous namespace + +asl::status_or> asl::parse_uint8(string_view sv, int base) +{ + return parse_integer(sv, base); +} + +asl::status_or> asl::parse_uint16(string_view sv, int base) +{ + return parse_integer(sv, base); +} + +asl::status_or> asl::parse_uint32(string_view sv, int base) +{ + return parse_integer(sv, base); +} + +asl::status_or> asl::parse_uint64(string_view sv, int base) +{ + return parse_integer(sv, base); +} + +asl::status_or> asl::parse_int8(string_view sv, int base) +{ + return parse_integer(sv, base); +} + +asl::status_or> asl::parse_int16(string_view sv, int base) +{ + return parse_integer(sv, base); +} + +asl::status_or> asl::parse_int32(string_view sv, int base) +{ + return parse_integer(sv, base); +} + +asl::status_or> asl::parse_int64(string_view sv, int base) +{ + return parse_integer(sv, base); +} + -- cgit