Skip to content

Commit cbdfb78

Browse files
committed
Switching from Span to Memory
This is to help the next layer, the parser, do its job a bit easier
1 parent dd210cb commit cbdfb78

5 files changed

Lines changed: 214 additions & 190 deletions

File tree

src/TurnerSoftware.CascadingStyles/CssToken.cs

Lines changed: 0 additions & 81 deletions
This file was deleted.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System;
2+
using System.ComponentModel;
3+
4+
#pragma warning disable 0809 // Obsolete member overrides non-obsolete member
5+
6+
namespace TurnerSoftware.CascadingStyles.Parsing
7+
{
8+
public struct CssToken
9+
{
10+
public readonly ReadOnlyMemory<char> RawValue;
11+
public readonly CssTokenType Type;
12+
public readonly CssTokenFlag Flags;
13+
public readonly ReadOnlyMemory<char> RawSecondaryValue;
14+
15+
public static CssToken EndOfFile => new(CssTokenType.EndOfFile);
16+
17+
public CssToken(CssTokenType type)
18+
{
19+
RawValue = default;
20+
Type = type;
21+
Flags = CssTokenFlag.None;
22+
RawSecondaryValue = default;
23+
}
24+
25+
public CssToken(ReadOnlyMemory<char> rawValue, CssTokenType type, CssTokenFlag flags = CssTokenFlag.None, ReadOnlyMemory<char> secondaryValue = default)
26+
{
27+
RawValue = rawValue;
28+
Type = type;
29+
Flags = flags;
30+
RawSecondaryValue = secondaryValue;
31+
}
32+
33+
public static bool operator !=(CssToken left, CssToken right) => !(left == right);
34+
35+
public static bool operator ==(CssToken left, CssToken right) =>
36+
left.RawValue.Equals(right) &&
37+
left.Type == right.Type &&
38+
left.Flags == right.Flags &&
39+
left.RawSecondaryValue.Equals(right);
40+
41+
[Obsolete("Equals() on CssToken will always throw an exception. Use == instead.")]
42+
[EditorBrowsable(EditorBrowsableState.Never)]
43+
public override bool Equals(object obj) => throw new NotSupportedException();
44+
[Obsolete("GetHashCode() on CssToken will always throw an exception.")]
45+
[EditorBrowsable(EditorBrowsableState.Never)]
46+
public override int GetHashCode() => throw new NotSupportedException();
47+
}
48+
49+
public enum CssTokenType
50+
{
51+
EndOfFile,
52+
Identifier,
53+
Function,
54+
AtKeyword,
55+
Hash,
56+
String,
57+
BadString,
58+
Url,
59+
BadUrl,
60+
Delimiter,
61+
Number,
62+
Percentage,
63+
Dimension,
64+
Whitespace,
65+
CDO,
66+
CDC,
67+
Colon,
68+
Semicolon,
69+
Comma,
70+
LeftSquareBracket,
71+
RightSquareBracket,
72+
LeftParenthesis,
73+
RightParenthesis,
74+
LeftCurlyBracket,
75+
RightCurlyBracket
76+
}
77+
78+
public enum CssTokenFlag
79+
{
80+
/// <summary>
81+
/// No token flag is set
82+
/// </summary>
83+
None,
84+
/// <summary>
85+
/// The default flag for &lt;hash-token&gt;.
86+
/// </summary>
87+
Hash_Unrestricted,
88+
/// <summary>
89+
/// Defines that the &lt;hash-token&gt; is a valid ID.
90+
/// </summary>
91+
Hash_Id,
92+
/// <summary>
93+
/// Whether a &lt;number-token> or &lt;dimension&gt; is a whole number.
94+
/// </summary>
95+
Number_Integer,
96+
/// <summary>
97+
/// Whether a &lt;number-token> or &lt;dimension&gt; is a fraction.
98+
/// </summary>
99+
Number_Number
100+
}
101+
}

src/TurnerSoftware.CascadingStyles/CssReader.cs renamed to src/TurnerSoftware.CascadingStyles/Parsing/CssTokenizer.cs

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using System;
22

3-
namespace TurnerSoftware.CascadingStyles
3+
namespace TurnerSoftware.CascadingStyles.Parsing
44
{
55
/// <summary>
66
/// Provides CSS tokenization support, closely following the <a href="https://drafts.csswg.org/css-syntax/#tokenization">official CSS specification</a>.
@@ -15,15 +15,15 @@ namespace TurnerSoftware.CascadingStyles
1515
/// These deviations from the standard allow the tokenizer to be allocation-free in its processing.
1616
/// </para>
1717
/// </remarks>
18-
public ref struct CssReader
18+
public struct CssTokenizer
1919
{
2020
public const char EndOfFile = char.MaxValue;
2121

22-
private readonly ReadOnlySpan<char> InputStream;
22+
private readonly ReadOnlyMemory<char> InputStream;
2323
private int RegionStartIndex;
2424
private int IndexOffset;
2525

26-
public CssReader(ReadOnlySpan<char> value)
26+
public CssTokenizer(ReadOnlyMemory<char> value)
2727
{
2828
InputStream = value;
2929
RegionStartIndex = 0;
@@ -38,7 +38,7 @@ private char Current
3838
{
3939
if (CurrentIndex < InputStream.Length)
4040
{
41-
return InputStream[CurrentIndex];
41+
return InputStream.Span[CurrentIndex];
4242
}
4343

4444
return EndOfFile;
@@ -48,14 +48,14 @@ private char Current
4848
/// <summary>
4949
/// The unconsumed "remaining" input stream data
5050
/// </summary>
51-
private ReadOnlySpan<char> RemainingInputStream => InputStream[CurrentIndex..];
51+
private ReadOnlyMemory<char> RemainingInputStream => InputStream.Slice(CurrentIndex);
5252

5353
private char Peek(int distance = 1)
5454
{
5555
var nextIndex = CurrentIndex + distance;
5656
if (nextIndex < InputStream.Length)
5757
{
58-
return InputStream[nextIndex];
58+
return InputStream.Span[nextIndex];
5959
}
6060

6161
return EndOfFile;
@@ -65,17 +65,17 @@ private char Peek(int distance = 1)
6565
/// Returns the region starting from <see cref="RegionStartIndex"/> to, but not including, <see cref="CurrentIndex"/>.
6666
/// </summary>
6767
/// <returns></returns>
68-
private ReadOnlySpan<char> GetRegion()
68+
private ReadOnlyMemory<char> GetRegion()
6969
{
70-
return InputStream[RegionStartIndex..CurrentIndex];
70+
return InputStream.Slice(RegionStartIndex, IndexOffset);
7171
}
7272

7373
/// <summary>
7474
/// Finalizes the current region and starts a new region.
7575
/// Region is from <see cref="RegionStartIndex"/> to, but not including, <see cref="CurrentIndex"/>.
7676
/// </summary>
7777
/// <returns></returns>
78-
private ReadOnlySpan<char> ConsumeRegion()
78+
private ReadOnlyMemory<char> ConsumeRegion()
7979
{
8080
var result = GetRegion();
8181
StartNewCaptureRegion();
@@ -136,7 +136,7 @@ public bool NextToken(out CssToken token)
136136
{
137137
if (RegionStartIndex == InputStream.Length)
138138
{
139-
token = default;
139+
token = CssToken.EndOfFile;
140140
return false;
141141
}
142142

@@ -248,7 +248,7 @@ public bool NextToken(out CssToken token)
248248
}
249249
else
250250
{
251-
token = default;
251+
token = CssToken.EndOfFile;
252252
return false;
253253
}
254254
break;
@@ -266,7 +266,7 @@ private void ConsumeCommentsIfAny()
266266
// followed by a U+002F SOLIDUS (/), or up to an EOF code point.
267267
if (Current == '/' && Peek() == '*')
268268
{
269-
var matchIndex = RemainingInputStream.IndexOf("*/");
269+
var matchIndex = RemainingInputStream.Span.IndexOf("*/");
270270
// No closing comment found / it closes at the end of the file
271271
if (matchIndex == -1)
272272
{
@@ -531,7 +531,7 @@ private CssToken ConsumeIdentifierLikeToToken()
531531
var identifier = ConsumeIdentifier();
532532

533533
// If identifier matches "url" (case-insensitive) and the next input code point is U+0028 LEFT PARENTHESIS, consume it
534-
if (identifier.Equals("url", StringComparison.OrdinalIgnoreCase) && Current == '(')
534+
if (identifier.Span.Equals("url", StringComparison.OrdinalIgnoreCase) && Current == '(')
535535
{
536536
ConsumeCurrent();
537537

@@ -654,10 +654,12 @@ private CssToken ConsumeUrlToToken()
654654
/// <returns></returns>
655655
private CssToken ConsumeRemnantsOfBadUrlToToken()
656656
{
657+
var remainingInputSpan = RemainingInputStream.Span;
658+
657659
// Repeatedly consume the next input code point from the stream:
658660
while (true)
659661
{
660-
var nextRightParenthesis = RemainingInputStream.IndexOf(')');
662+
var nextRightParenthesis = remainingInputSpan.IndexOf(')');
661663

662664
// If a U+0029 RIGHT PARENTHESIS ()) or EOF is found, consume and return
663665
// In this case, if no right parenthesis is found, we can jump to the EOF
@@ -671,7 +673,7 @@ private CssToken ConsumeRemnantsOfBadUrlToToken()
671673
// Consume an escaped code point. This allows an escaped right parenthesis ("\)")
672674
// to be encountered without ending the <bad-url-token>.
673675
// We backtrack from the index of the right parenthesis to check
674-
var isEscaped = nextRightParenthesis > 0 && RemainingInputStream[nextRightParenthesis - 1] == '\\';
676+
var isEscaped = nextRightParenthesis > 0 && remainingInputSpan[nextRightParenthesis - 1] == '\\';
675677

676678
// Either way, we consume all the characters we've found
677679
ConsumeNext(nextRightParenthesis + 1);
@@ -693,7 +695,7 @@ private CssToken ConsumeRemnantsOfBadUrlToToken()
693695
/// </summary>
694696
/// <param name="seenDecimal"></param>
695697
/// <returns></returns>
696-
private void ConsumeNumber(out ReadOnlySpan<char> number, out CssTokenFlag flags)
698+
private void ConsumeNumber(out ReadOnlyMemory<char> number, out CssTokenFlag flags)
697699
{
698700
var startIndex = CurrentIndex;
699701

@@ -757,7 +759,7 @@ private void ConsumeNumber(out ReadOnlySpan<char> number, out CssTokenFlag flags
757759
/// Assumes that <see cref="Current"/> is the start of the identifier.
758760
/// </remarks>
759761
/// <returns></returns>
760-
private ReadOnlySpan<char> ConsumeIdentifier()
762+
private ReadOnlyMemory<char> ConsumeIdentifier()
761763
{
762764
var startIndex = CurrentIndex;
763765
while (true)

src/TurnerSoftware.CascadingStyles/TokenizationHelper.cs renamed to src/TurnerSoftware.CascadingStyles/Parsing/TokenizationHelper.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Text;
33

4-
namespace TurnerSoftware.CascadingStyles
4+
namespace TurnerSoftware.CascadingStyles.Parsing
55
{
66
public static class TokenizationHelper
77
{
@@ -98,13 +98,13 @@ public static string UnescapeValue(ReadOnlySpan<char> value)
9898
return value.ToString();
9999
}
100100

101-
public static string UnescapeValue(this CssToken token) => UnescapeValue(token.RawValue);
101+
public static string UnescapeValue(this CssToken token) => UnescapeValue(token.RawValue.Span);
102102

103-
public static string UnescapeSecondaryValue(this CssToken token) => UnescapeValue(token.RawSecondaryValue);
103+
public static string UnescapeSecondaryValue(this CssToken token) => UnescapeValue(token.RawSecondaryValue.Span);
104104

105105
public static decimal GetNumber(this CssToken token) => token.Type switch
106106
{
107-
CssTokenType.Number or CssTokenType.Dimension => decimal.Parse(token.RawValue, System.Globalization.NumberStyles.Number),
107+
CssTokenType.Number or CssTokenType.Dimension => decimal.Parse(token.RawValue.Span, System.Globalization.NumberStyles.Number),
108108
_ => throw new InvalidOperationException("Token is not of a valid type to decode a number from"),
109109
};
110110
}

0 commit comments

Comments
 (0)