Skip to content

Commit 8c48bc9

Browse files
committed
Add _rs signed range literals
1 parent fdf2180 commit 8c48bc9

File tree

2 files changed

+159
-15
lines changed

2 files changed

+159
-15
lines changed

subspace/ops/range_literals.h

Lines changed: 109 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,23 @@
1414

1515
#pragma once
1616

17+
#include <type_traits>
18+
1719
#include "subspace/assertions/check.h"
20+
#include "subspace/num/signed_integer.h"
1821
#include "subspace/num/unsigned_integer.h"
1922
#include "subspace/ops/range.h"
2023

2124
namespace sus::ops {
2225

2326
namespace __private {
2427

28+
template <bool IsSigned>
2529
struct RangeLiteralDeducer {
30+
using Int =
31+
std::conditional_t<IsSigned, ::sus::num::isize, ::sus::num::usize>;
32+
using Digit = std::conditional_t<IsSigned, int, unsigned int>;
33+
2634
// A non-consteval method used to indicate failure in parsing, which produces
2735
// a compiler error and still works in the presence of -fno-exceptions unlike
2836
// `throw`.
@@ -32,17 +40,41 @@ struct RangeLiteralDeducer {
3240
size_t i = 0u;
3341

3442
bool has_lower = false;
43+
bool negate_lower = false;
3544
if (c[i] != '.') {
45+
if constexpr (IsSigned) {
46+
if (c[i] == '-') {
47+
negate_lower = true;
48+
i += 1u;
49+
}
50+
}
3651
if (c[i] < '0' || c[i] > '9')
3752
invalid("Invalid lower bound number in range literal");
38-
lower = static_cast<unsigned int>(c[i] - '0');
53+
{
54+
auto digit = static_cast<Digit>(c[i] - '0');
55+
if constexpr (IsSigned) {
56+
if (negate_lower) digit = -digit;
57+
}
58+
lower = digit;
59+
}
3960
i += 1u;
4061

4162
for (; c[i] != '\0' && c[i] != '.'; i += 1u) {
4263
if (c[i] != '\'') {
4364
if (c[i] < '0' || c[i] > '9')
4465
invalid("Invalid lower bound number in range literal");
45-
lower = lower * 10u + static_cast<unsigned int>(c[i] - '0');
66+
auto mul = lower.checked_mul(Digit{10});
67+
if (mul.is_none()) invalid("Lower bound is out of range");
68+
lower = ::sus::move(mul).unwrap();
69+
{
70+
auto digit = static_cast<Digit>(c[i] - '0');
71+
if constexpr (IsSigned) {
72+
if (negate_lower) digit = -digit;
73+
}
74+
auto add = lower.checked_add(digit);
75+
if (add.is_none()) invalid("Lower bound is out of range");
76+
lower = ::sus::move(add).unwrap();
77+
}
4678
} else if (c[i + 1] < '0' || c[i + 1] > '9') {
4779
invalid("Invalid lower bound number in range literal");
4880
}
@@ -73,16 +105,40 @@ struct RangeLiteralDeducer {
73105
i += 1u;
74106
}
75107

108+
bool negate_upper = false;
109+
if constexpr (IsSigned) {
110+
if (c[i] == '-') {
111+
negate_upper = true;
112+
i += 1u;
113+
}
114+
}
76115
if (c[i] < '0' || c[i] > '9')
77116
invalid("Invalid upper bound number in range literal");
78-
upper = static_cast<unsigned int>(c[i] - '0');
117+
{
118+
auto digit = static_cast<Digit>(c[i] - '0');
119+
if constexpr (IsSigned) {
120+
if (negate_upper) digit = -digit;
121+
}
122+
upper = digit;
123+
}
79124
i += 1u;
80125

81126
for (; c[i] != '\0'; i += 1u) {
82127
if (c[i] != '\'') {
83128
if (c[i] < '0' || c[i] > '9')
84129
invalid("Invalid upper bound number in range literal");
85-
upper = upper * 10u + static_cast<unsigned int>(c[i] - '0');
130+
auto mul = upper.checked_mul(Digit{10});
131+
if (mul.is_none()) invalid("Upper bound is out of range");
132+
upper = ::sus::move(mul).unwrap();
133+
{
134+
auto digit = static_cast<Digit>(c[i] - '0');
135+
if constexpr (IsSigned) {
136+
if (negate_upper) digit = -digit;
137+
}
138+
auto add = upper.checked_add(digit);
139+
if (add.is_none()) invalid("Upper bound is out of range");
140+
upper = ::sus::move(add).unwrap();
141+
}
86142
} else if (c[i + 1] < '0' || c[i + 1] > '9') {
87143
invalid("Invalid upper bound number in range literal");
88144
}
@@ -104,8 +160,8 @@ struct RangeLiteralDeducer {
104160
UpperBound,
105161
LowerAndUpperBound,
106162
} type;
107-
::sus::num::usize lower;
108-
::sus::num::usize upper;
163+
Int lower;
164+
Int upper;
109165
};
110166

111167
} // namespace __private
@@ -114,23 +170,62 @@ struct RangeLiteralDeducer {
114170

115171
/// Returns a range that satisfies the `RangeBounds<usize>` concept.
116172
///
173+
/// Because `usize` is unsigned, numbers may not be negative.
174+
///
117175
/// The syntax is:
118-
/// * `start..end` for a range including start and excluding end.
119-
/// * `start..=end` for a range including start and including end.
120-
/// * `start..` for a range including start and never ending.
176+
/// * `start..end` for a range including start and excluding end. This returns a
177+
/// `sus::ops::Range<usize>`.
178+
/// * `start..=end` for a range including start and including end. This returns
179+
/// a `sus::ops::Range<usize>`.
180+
/// * `start..` for a range including start and never ending. This returns a
181+
/// `sus::ops::RangeFrom<usize>`.
182+
/// * `..end` for a range including everything up end. This returns a
183+
/// `sus::ops::RangeTo<usize>`.
184+
/// * `..=end` for a range including everything up and including end. This
185+
/// returns a `sus::ops::RangeTo<usize>`.
121186
/// * `..` for a range that has no bounds at all. Typically for a slicing range
122-
/// to indicate the entire slice.
123-
template <::sus::ops::__private::RangeLiteralDeducer D>
187+
/// to indicate the entire slice. This returns a `sus::ops::RangeFull<usize>`.
188+
template <::sus::ops::__private::RangeLiteralDeducer<false> D>
124189
constexpr auto operator""_r() {
125190
using ::sus::ops::__private::RangeLiteralDeducer;
126-
if constexpr (D.type == RangeLiteralDeducer::NoBound)
191+
if constexpr (D.type == RangeLiteralDeducer<false>::NoBound)
127192
return ::sus::ops::RangeFull<::sus::num::usize>();
128-
else if constexpr (D.type == RangeLiteralDeducer::LowerBound)
193+
else if constexpr (D.type == RangeLiteralDeducer<false>::LowerBound)
129194
return ::sus::ops::RangeFrom<::sus::num::usize>(D.lower);
130-
else if constexpr (D.type == RangeLiteralDeducer::UpperBound)
195+
else if constexpr (D.type == RangeLiteralDeducer<false>::UpperBound)
131196
return ::sus::ops::RangeTo<::sus::num::usize>(D.upper);
132197
else
133198
return ::sus::ops::Range<::sus::num::usize>(D.lower, D.upper);
134199
}
135200

201+
/// Returns a range that satisfies the `RangeBounds<isize>` concept.
202+
///
203+
/// Numbers may be positive or negative.
204+
///
205+
/// The syntax is:
206+
/// * `start..end` for a range including start and excluding end. This returns a
207+
/// `sus::ops::Range<isize>`.
208+
/// * `start..=end` for a range including start and including end. This returns
209+
/// a `sus::ops::Range<isize>`.
210+
/// * `start..` for a range including start and never ending. This returns a
211+
/// `sus::ops::RangeFrom<isize>`.
212+
/// * `..end` for a range including everything up end. This returns a
213+
/// `sus::ops::RangeTo<isize>`.
214+
/// * `..=end` for a range including everything up and including end. This
215+
/// returns a `sus::ops::RangeTo<isize>`.
216+
/// * `..` for a range that has no bounds at all. Typically for a slicing range
217+
/// to indicate the entire slice. This returns a `sus::ops::RangeFull<isize>`.
218+
template <::sus::ops::__private::RangeLiteralDeducer<true> D>
219+
constexpr auto operator""_rs() {
220+
using ::sus::ops::__private::RangeLiteralDeducer;
221+
if constexpr (D.type == RangeLiteralDeducer<true>::NoBound)
222+
return ::sus::ops::RangeFull<::sus::num::isize>();
223+
else if constexpr (D.type == RangeLiteralDeducer<true>::LowerBound)
224+
return ::sus::ops::RangeFrom<::sus::num::isize>(D.lower);
225+
else if constexpr (D.type == RangeLiteralDeducer<true>::UpperBound)
226+
return ::sus::ops::RangeTo<::sus::num::isize>(D.upper);
227+
else
228+
return ::sus::ops::Range<::sus::num::isize>(D.lower, D.upper);
229+
}
230+
136231
// TODO: _rs to make a signed RangeBounds over `isize`?

subspace/ops/range_unittest.cc

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,9 +85,16 @@ static_assert(std::same_as<decltype("1.."_r), sus::ops::RangeFrom<usize>>);
8585
static_assert(std::same_as<decltype("..2"_r), sus::ops::RangeTo<usize>>);
8686
static_assert(std::same_as<decltype("1..2"_r), sus::ops::Range<usize>>);
8787
static_assert(std::same_as<decltype("1..=2"_r), sus::ops::Range<usize>>);
88+
// SIgned.
89+
static_assert(std::same_as<decltype(".."_rs), sus::ops::RangeFull<isize>>);
90+
static_assert(std::same_as<decltype("1.."_rs), sus::ops::RangeFrom<isize>>);
91+
static_assert(std::same_as<decltype("..2"_rs), sus::ops::RangeTo<isize>>);
92+
static_assert(std::same_as<decltype("1..2"_rs), sus::ops::Range<isize>>);
93+
static_assert(std::same_as<decltype("1..=2"_rs), sus::ops::Range<isize>>);
8894

89-
// Start and end bounds.
9095
// clang-format off
96+
97+
// Start and end bounds.
9198
static_assert([]() constexpr {auto r = ".."_r; return r.start_bound().is_none(); }());
9299
static_assert([]() constexpr { auto r = ".."_r; return r.end_bound().is_none(); }());
93100
static_assert([]() constexpr { auto r = "3.."_r; return r.start_bound().unwrap() == 3_usize; }());
@@ -100,6 +107,31 @@ static_assert([]() constexpr { auto r = "3..8"_r; return r.start_bound().unwrap(
100107
static_assert([]() constexpr { auto r = "3..8"_r; return r.end_bound().unwrap() == 8_usize; }());
101108
static_assert([]() constexpr { auto r = "3..=8"_r; return r.start_bound().unwrap() == 3_usize; }());
102109
static_assert([]() constexpr { auto r = "3..=8"_r; return r.end_bound().unwrap() == 9_usize; }());
110+
// Signed
111+
static_assert([]() constexpr {auto r = ".."_rs; return r.start_bound().is_none(); }());
112+
static_assert([]() constexpr { auto r = ".."_rs; return r.end_bound().is_none(); }());
113+
static_assert([]() constexpr { auto r = "3.."_rs; return r.start_bound().unwrap() == 3_isize; }());
114+
static_assert([]() constexpr { auto r = "3.."_rs; return r.end_bound().is_none(); }());
115+
static_assert([]() constexpr { auto r = "-3.."_rs; return r.start_bound().unwrap() == -3_isize; }());
116+
static_assert([]() constexpr { auto r = "-3.."_rs; return r.end_bound().is_none(); }());
117+
static_assert([]() constexpr { auto r = "..3"_rs; return r.start_bound().is_none(); }());
118+
static_assert([]() constexpr { auto r = "..3"_rs; return r.end_bound().unwrap() == 3_isize; }());
119+
static_assert([]() constexpr { auto r = "..-3"_rs; return r.start_bound().is_none(); }());
120+
static_assert([]() constexpr { auto r = "..-3"_rs; return r.end_bound().unwrap() == -3_isize; }());
121+
static_assert([]() constexpr { auto r = "..=3"_rs; return r.start_bound().is_none(); }());
122+
static_assert([]() constexpr { auto r = "..=3"_rs; return r.end_bound().unwrap() == 4_isize; }());
123+
static_assert([]() constexpr { auto r = "..=-3"_rs; return r.start_bound().is_none(); }());
124+
static_assert([]() constexpr { auto r = "..=-3"_rs; return r.end_bound().unwrap() == -2_isize; }());
125+
static_assert([]() constexpr { auto r = "-8..3"_rs; return r.start_bound().unwrap() == -8_isize; }());
126+
static_assert([]() constexpr { auto r = "-8..3"_rs; return r.end_bound().unwrap() == 3_isize; }());
127+
static_assert([]() constexpr { auto r = "-8..-3"_rs; return r.start_bound().unwrap() == -8_isize; }());
128+
static_assert([]() constexpr { auto r = "-8..-3"_rs; return r.end_bound().unwrap() == -3_isize; }());
129+
static_assert([]() constexpr { auto r = "-8..=-3"_rs; return r.start_bound().unwrap() == -8_isize; }());
130+
static_assert([]() constexpr { auto r = "-8..=-3"_rs; return r.end_bound().unwrap() == -2_isize; }());
131+
static_assert([]() constexpr { auto r = "-8..=-1"_rs; return r.end_bound().unwrap() == 0_isize; }());
132+
static_assert([]() constexpr { auto r = "-8..=-0"_rs; return r.end_bound().unwrap() == 1_isize; }());
133+
static_assert([]() constexpr { auto r = "-8..=0"_rs; return r.end_bound().unwrap() == 1_isize; }());
134+
103135
// clang-format on
104136

105137
// Parsing of numbers.
@@ -119,6 +151,23 @@ static_assert([]() constexpr {
119151
auto r = "3'4'5'6..87'654'3"_r;
120152
return r.end_bound().unwrap() == 876543_usize;
121153
}());
154+
// Signed.
155+
static_assert([]() constexpr {
156+
auto r = "345678..876543"_rs;
157+
return r.start_bound().unwrap() == 345678_isize;
158+
}());
159+
static_assert([]() constexpr {
160+
auto r = "345678..876543"_rs;
161+
return r.end_bound().unwrap() == 876543_isize;
162+
}());
163+
static_assert([]() constexpr {
164+
auto r = "-3'4'5'6..87'654'3"_rs;
165+
return r.start_bound().unwrap() == -3456_isize;
166+
}());
167+
static_assert([]() constexpr {
168+
auto r = "3'4'5'6..-87'654'3"_rs;
169+
return r.end_bound().unwrap() == -876543_isize;
170+
}());
122171

123172
// None of these compile.
124173
// TODO: No-compile tests of some sort? Can't test this with a concept.

0 commit comments

Comments
 (0)