aboutsummaryrefslogtreecommitdiffstats
path: root/nihil.util/parse_size.ccm
blob: c6925781e8e4882201ea4cebc62cfe169076255a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
/*
 * This source code is released into the public domain.
 */

module;

#include <algorithm>
#include <coroutine>
#include <cstdint>
#include <expected>
#include <ranges>
#include <string>
#include <system_error>
#include <utility>

export module nihil.util:parse_size;

import nihil.core;
import nihil.error;
import nihil.monad;

import :ctype;

namespace nihil {

template<typename Char>
auto get_multiplier(Char c) -> std::expected<std::uint64_t, error>
{
	auto ret = std::uint64_t{1};

	switch (c) {
	case 'p': case 'P': ret *= 1024;
	case 't': case 'T': ret *= 1024;
	case 'g': case 'G': ret *= 1024;
	case 'm': case 'M': ret *= 1024;
	case 'k': case 'K': ret *= 1024;
		return ret;

	default:
		return std::unexpected(error(errc::invalid_unit));
	}
}

/*
 * Parse a string containing a human-formatted size, such as "1024"
 * or "4g".  Parsing is always done in the "C" locale and does not
 * recognise thousands separators or negative numbers.
 */
export template<typename T, typename Char> [[nodiscard]]
auto parse_size(std::basic_string_view<Char> str)
	-> std::expected<T, error>
{
	// Extract the numeric part of the string.
	auto it = std::ranges::find_if_not(str, is_c_digit);
	auto num_str = std::basic_string_view<Char>(
				std::ranges::begin(str), it);

	if (num_str.empty())
		co_return std::unexpected(error(errc::empty_string));

	auto ret = T{0};

	for (auto c : num_str) {
		if (ret > (std::numeric_limits<T>::max() / 10))
			co_return std::unexpected(error(
					std::errc::result_out_of_range));
		ret *= 10;

		auto digit = static_cast<T>(c - '0');
		if ((std::numeric_limits<T>::max() - digit) < ret)
			co_return std::unexpected(error(
					std::errc::result_out_of_range));
		ret += digit;
	}

	if (it == str.end())
		// No multiplier.
		co_return ret;

	auto mchar = *it++;

	if (it != str.end())
		// Multiplier is more than one character.
		co_return std::unexpected(error(errc::invalid_unit));

	auto mult = co_await get_multiplier(mchar);

	if (std::cmp_greater(ret, std::numeric_limits<T>::max() / mult))
		co_return std::unexpected(error(
				std::errc::result_out_of_range));

	co_return ret * mult;
}

export template<typename T>
[[nodiscard]] inline auto parse_size(char const *s)
{
	return parse_size<T>(std::string_view(s));
}

export template<typename T>
[[nodiscard]] inline auto parse_size(wchar_t const *s)
{
	return parse_size<T>(std::wstring_view(s));
}

}