dimval 0.2.0
Modern C++23 header-only library of dimensional values (units, measures, ranges)
Loading...
Searching...
No Matches
parse_detail.hpp
Go to the documentation of this file.
1#pragma once
2
6
7#include <dimval/core.hpp>
8
9#include <cerrno>
10#include <charconv>
11#include <expected>
12#include <string>
13#include <string_view>
14#include <system_error>
15#include <type_traits>
16
17namespace dimval::detail {
18
21 std::string_view number;
22 std::string_view tail;
23 std::size_t number_end{0};
24};
25
26[[nodiscard]] inline std::expected<SplitResult, ParseError>
27split_number_and_tail(std::string_view s) {
28 const auto orig = s;
29 s = trim(s);
30 if (s.empty()) {
31 return std::unexpected{
32 ParseError{ParseErrorCode::Empty, std::string{orig}, 0, "empty input"}};
33 }
34 // Find the end of the numeric portion. Allow sign, digits, '.', exponent.
35 std::size_t i = 0;
36 if (i < s.size() && (s[i] == '+' || s[i] == '-')) {
37 ++i;
38 }
39 bool seen_digit = false;
40 while (i < s.size() && s[i] >= '0' && s[i] <= '9') {
41 ++i;
42 seen_digit = true;
43 }
44 if (i < s.size() && s[i] == '.') {
45 ++i;
46 while (i < s.size() && s[i] >= '0' && s[i] <= '9') {
47 ++i;
48 seen_digit = true;
49 }
50 }
51 if (i < s.size() && (s[i] == 'e' || s[i] == 'E')) {
52 ++i;
53 if (i < s.size() && (s[i] == '+' || s[i] == '-')) {
54 ++i;
55 }
56 while (i < s.size() && s[i] >= '0' && s[i] <= '9') {
57 ++i;
58 }
59 }
60 if (!seen_digit) {
61 return std::unexpected{
62 ParseError{ParseErrorCode::InvalidNumber, std::string{orig}, 0, "no digits in input"}};
63 }
64 SplitResult out;
65 out.number = s.substr(0, i);
66 out.tail = trim(s.substr(i));
67 out.number_end = i;
68 return out;
69}
70
71template <typename T>
72[[nodiscard]] inline std::expected<T, ParseError> parse_number(const std::string_view num,
73 const std::string_view orig_input) {
74 if constexpr (std::is_integral_v<T>) {
75 T out{};
76 const auto* const first = num.data();
77 const auto* const last = first + num.size();
78 if (auto res = std::from_chars(first, last, out);
79 res.ec != std::errc{} || res.ptr != last) {
80 return std::unexpected{ParseError{ParseErrorCode::InvalidNumber,
81 std::string{orig_input},
82 0,
83 "failed to parse number"}};
84 }
85 return out;
86 } else {
87 // libc++ on macOS gates std::from_chars for floating-point on
88 // macOS 26.0+; fall back to strtod for portability. Copy into a
89 // null-terminated buffer so strtod sees a clean boundary.
90 const std::string buf{num};
91 char* endp = nullptr;
92 errno = 0;
93 const double d = std::strtod(buf.c_str(), &endp);
94 if (endp != buf.c_str() + buf.size() || errno != 0) {
95 return std::unexpected{ParseError{ParseErrorCode::InvalidNumber,
96 std::string{orig_input},
97 0,
98 "failed to parse number"}};
99 }
100 return static_cast<T>(d);
101 }
102}
103
104} // namespace dimval::detail
Common concepts, error types, and small utilities used across dimval.
Detailed error report from a parse_*_value call.
Definition core.hpp:30
Split "<number><whitespace><tail>" into (number string, tail).
Definition parse_detail.hpp:20
std::size_t number_end
index in original input where the number ended
Definition parse_detail.hpp:23