1/*
2 * LegacyClonk
3 *
4 * Copyright (c) 2023, The LegacyClonk Team and contributors
5 *
6 * Distributed under the terms of the ISC license; see accompanying file
7 * "COPYING" for details.
8 *
9 * "Clonk" is a registered trademark of Matthes Bender, used with permission.
10 * See accompanying file "TRADEMARK" for details.
11 *
12 * To redistribute this file separately, substitute the full license texts
13 * for the above references.
14 */
15
16#pragma once
17
18#include <charconv>
19#include <concepts>
20#include <format>
21#include <stdexcept>
22#include <string>
23#include <utility>
24
25template <typename T>
26struct NumberRangeError : public std::range_error
27{
28 NumberRangeError(std::string message, const T atRangeLimit) : std::range_error{std::move(message)}, AtRangeLimit{atRangeLimit} {}
29
30 T AtRangeLimit;
31};
32
33template <std::integral T>
34T ParseNumber(std::string_view s, const unsigned base = 10)
35{
36 if (s.starts_with(x: '+'))
37 {
38 s.remove_prefix(n: 1);
39 }
40
41 T result{};
42 const auto sEnd = s.data() + s.size();
43 const auto [end, ec] = std::from_chars(s.data(), sEnd, result, base);
44 if (ec == std::errc{} && end == sEnd)
45 {
46 return result;
47 }
48
49 if (ec == std::errc{} && end != sEnd)
50 {
51 throw std::invalid_argument{std::format(fmt: "ParseNumber: Unexpected garbage after integer: \"{}\"", args: std::string_view{end, sEnd})};
52 }
53
54 if (ec == std::errc::result_out_of_range)
55 {
56 using limits = std::numeric_limits<T>;
57 const auto limit = (std::signed_integral<T> && s.starts_with(x: '-')) ? limits::min() : limits::max();
58 throw NumberRangeError<T>{std::format(fmt: "ParseNumber: Value out of range: \"{}\"", args&: s), limit};
59 }
60
61 throw std::invalid_argument{std::format(fmt: "ParseNumber: Not a number: \"{}\"", args&: s)};
62}
63