Skip to content

Commit a6c08e5

Browse files
committed
implement ISpanParseable
1 parent 8e03bfa commit a6c08e5

File tree

2 files changed

+136
-25
lines changed

2 files changed

+136
-25
lines changed

src/Smdn.Fundamental.Uuid/Smdn.Formats.UniversallyUniqueIdentifiers/Node.IParseable.cs

Lines changed: 56 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,17 @@ partial struct Node
1212
#pragma warning restore IDE0040
1313
#if FEATURE_GENERIC_MATH
1414
:
15-
IParseable<Node>
15+
IParseable<Node>,
16+
ISpanParseable<Node>
1617
#endif
1718
{
1819
public static Node Parse(string s, IFormatProvider? provider = null)
19-
=> TryParse(s ?? throw new ArgumentNullException(nameof(s)), provider, out var result)
20+
=> TryParse((s ?? throw new ArgumentNullException(nameof(s))).AsSpan(), provider, out var result)
21+
? result
22+
: throw new FormatException("invalid format");
23+
24+
public static Node Parse(ReadOnlySpan<char> s, IFormatProvider? provider = null)
25+
=> TryParse(s, provider, out var result)
2026
? result
2127
: throw new FormatException("invalid format");
2228

@@ -27,32 +33,58 @@ public static bool TryParse(string? s, IFormatProvider? provider, out Node resul
2733
{
2834
result = default;
2935

30-
if (s is null)
31-
return false;
36+
return s is not null && TryParse(s.AsSpan(), provider, out result);
37+
}
38+
39+
public static bool TryParse(ReadOnlySpan<char> s, out Node result)
40+
=> TryParse(s, provider: null, out result);
3241

33-
var p =
34-
#if SYSTEM_STRING_SPLIT_CHAR
35-
s.Split(':', StringSplitOptions.None);
42+
public static bool TryParse(ReadOnlySpan<char> s, IFormatProvider? provider, out Node result)
43+
{
44+
result = default;
45+
46+
const char delimiter = ':';
47+
Span<byte> node = stackalloc byte[SizeOfSelf];
48+
49+
for (var n = 0; n < SizeOfSelf; n++) {
50+
ReadOnlySpan<char> span;
51+
52+
if (n < SizeOfSelf - 1) {
53+
var indexOfDelimiter = s.IndexOf(delimiter);
54+
55+
if (indexOfDelimiter < 0)
56+
return false;
57+
58+
span = s.Slice(0, indexOfDelimiter);
59+
s = s.Slice(indexOfDelimiter + 1);
60+
}
61+
else {
62+
span = s;
63+
}
64+
65+
if (
66+
!byte.TryParse(
67+
#pragma warning disable SA1114
68+
#if SYSTEM_INUMBER_TRYPARSE_READONLYSPAN_OF_CHAR
69+
span,
70+
#elif SYSTEM_STRING_CTOR_READONLYSPAN_OF_CHAR
71+
new string(span),
3672
#else
37-
s.Split(new[] { ':' }, StringSplitOptions.None);
73+
new string(span.ToArray()),
3874
#endif
75+
#pragma warning restore SA1114
76+
NumberStyles.HexNumber,
77+
provider: null,
78+
out var parsed
79+
)
80+
) {
81+
return false;
82+
}
83+
84+
node[n] = parsed;
85+
}
3986

40-
if (p.Length != SizeOfSelf)
41-
return false;
42-
if (!byte.TryParse(p[0], NumberStyles.HexNumber, provider: null, out var n0))
43-
return false;
44-
if (!byte.TryParse(p[1], NumberStyles.HexNumber, provider: null, out var n1))
45-
return false;
46-
if (!byte.TryParse(p[2], NumberStyles.HexNumber, provider: null, out var n2))
47-
return false;
48-
if (!byte.TryParse(p[3], NumberStyles.HexNumber, provider: null, out var n3))
49-
return false;
50-
if (!byte.TryParse(p[4], NumberStyles.HexNumber, provider: null, out var n4))
51-
return false;
52-
if (!byte.TryParse(p[5], NumberStyles.HexNumber, provider: null, out var n5))
53-
return false;
54-
55-
result = new(n0, n1, n2, n3, n4, n5);
87+
result = new(node);
5688

5789
return true;
5890
}

tests/Smdn.Fundamental.Uuid/Smdn.Formats.UniversallyUniqueIdentifiers/Node.IParseable.cs

Lines changed: 80 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ partial class NodeTests {
99
[Test]
1010
public void TestParse_ArgumentNull()
1111
{
12-
Assert.Throws<ArgumentNullException>(() => Node.Parse(null, provider: null));
12+
Assert.Throws<ArgumentNullException>(() => Node.Parse((string)null, provider: null));
1313

1414
#if FEATURE_GENERIC_MATH
1515
Assert.Throws<ArgumentNullException>(() => Parse<Node>(null, provider: null), "IParseable");
@@ -61,6 +61,49 @@ public void TestParse(string s, bool expectValid, string expectedString)
6161
#endif
6262
}
6363

64+
[TestCase("00:00:00:00:00:00", true, "00:00:00:00:00:00")]
65+
[TestCase("01:23:45:67:89:AB", true, "01:23:45:67:89:AB")]
66+
[TestCase("FF:FF:FF:FF:FF:FF", true, "FF:FF:FF:FF:FF:FF")]
67+
[TestCase("ab:cd:ef:AB:CD:EF", true, "ab:cd:ef:AB:CD:EF")]
68+
[TestCase("0:1:2:3:4:F", true, "00:01:02:03:04:0F")]
69+
[TestCase("000:001:002:003:004:00F", true, "00:01:02:03:04:0F")]
70+
[TestCase("00:00:00:00:00", false, null)]
71+
[TestCase("00", false, null)]
72+
[TestCase("", false, null)]
73+
[TestCase("100:00:00:00:00:00", false, null)]
74+
[TestCase("00:00:00:00:00:100", false, null)]
75+
[TestCase("00:00:00:00:00:0X", false, null)]
76+
[TestCase("00-00-00-00-00-00", false, null)]
77+
[TestCase("00:00:00:00:00-00", false, null)]
78+
public void TestParse_ISpanParseable(string s, bool expectValid, string expectedString)
79+
{
80+
Node n = default;
81+
82+
if (expectValid)
83+
Assert.DoesNotThrow(() => n = Node.Parse(s.AsSpan(), provider: null));
84+
else
85+
Assert.Throws<FormatException>(() => n = Node.Parse(s.AsSpan(), provider: null));
86+
87+
if (expectValid) {
88+
Assert.AreEqual(expectedString.ToUpperInvariant(), n.ToString("X"));
89+
Assert.AreEqual(expectedString.ToLowerInvariant(), n.ToString("x"));
90+
}
91+
92+
#if FEATURE_GENERIC_MATH
93+
if (expectValid)
94+
Assert.DoesNotThrow(() => n = Parse<Node>(s.AsSpan(), provider: null), "IParseable");
95+
else
96+
Assert.Throws<FormatException>(() => n = Parse<Node>(s.AsSpan(), provider: null), "IParseable");
97+
98+
if (expectValid) {
99+
Assert.AreEqual(expectedString.ToUpperInvariant(), n.ToString("X"), "IParseable");
100+
Assert.AreEqual(expectedString.ToLowerInvariant(), n.ToString("x"), "IParseable");
101+
}
102+
103+
static T Parse<T>(ReadOnlySpan<char> s, IFormatProvider provider) where T : ISpanParseable<T> => T.Parse(s, provider);
104+
#endif
105+
}
106+
64107
[TestCase("00:00:00:00:00:00", true, "00:00:00:00:00:00")]
65108
[TestCase("01:23:45:67:89:AB", true, "01:23:45:67:89:AB")]
66109
[TestCase("FF:FF:FF:FF:FF:FF", true, "FF:FF:FF:FF:FF:FF")]
@@ -94,6 +137,42 @@ public void TestTryParse(string s, bool expectValid, string expectedString)
94137
}
95138

96139
static bool TryParse<T>(string s, out T result) where T : IParseable<T> => T.TryParse(s, provider: null, out result);
140+
#endif
141+
}
142+
143+
[TestCase("00:00:00:00:00:00", true, "00:00:00:00:00:00")]
144+
[TestCase("01:23:45:67:89:AB", true, "01:23:45:67:89:AB")]
145+
[TestCase("FF:FF:FF:FF:FF:FF", true, "FF:FF:FF:FF:FF:FF")]
146+
[TestCase("ab:cd:ef:AB:CD:EF", true, "ab:cd:ef:AB:CD:EF")]
147+
[TestCase("0:1:2:3:4:F", true, "00:01:02:03:04:0F")]
148+
[TestCase("000:001:002:003:004:00F", true, "00:01:02:03:04:0F")]
149+
[TestCase("00:00:00:00:00", false, null)]
150+
[TestCase("00", false, null)]
151+
[TestCase("", false, null)]
152+
[TestCase(null, false, null)]
153+
[TestCase("100:00:00:00:00:00", false, null)]
154+
[TestCase("00:00:00:00:00:100", false, null)]
155+
[TestCase("00:00:00:00:00:0X", false, null)]
156+
[TestCase("00-00-00-00-00-00", false, null)]
157+
[TestCase("00:00:00:00:00-00", false, null)]
158+
public void TestTryParse_ISpanParseable(string s, bool expectValid, string expectedString)
159+
{
160+
Assert.AreEqual(expectValid, Node.TryParse(s.AsSpan(), out var node));
161+
162+
if (expectValid) {
163+
Assert.AreEqual(expectedString.ToUpperInvariant(), node.ToString("X"));
164+
Assert.AreEqual(expectedString.ToLowerInvariant(), node.ToString("x"));
165+
}
166+
167+
#if FEATURE_GENERIC_MATH
168+
Assert.AreEqual(expectValid, TryParse<Node>(s.AsSpan(), out var node2), "IParseable");
169+
170+
if (expectValid) {
171+
Assert.AreEqual(expectedString.ToUpperInvariant(), node2.ToString("X"), "IParseable");
172+
Assert.AreEqual(expectedString.ToLowerInvariant(), node2.ToString("x"), "IParseable");
173+
}
174+
175+
static bool TryParse<T>(ReadOnlySpan<char> s, out T result) where T : ISpanParseable<T> => T.TryParse(s, provider: null, out result);
97176
#endif
98177
}
99178
}

0 commit comments

Comments
 (0)