Skip to content

Commit a1dd898

Browse files
committed
Get rid of LineTrackingStringBuffer class and instead use the line information provided by RazorSourceDocument.
This additionally gets rid of an extra whole buffer allocation in the ParserContext. The most complex bit of the change is around avoiding TextLineCollection.GetLocation. Overall, I'm seeing a pretty big win here, about 35% less time spent in RazorSyntaxTree.Parse for the typing scenario I was doing in a very large file.
1 parent 073cd0a commit a1dd898

File tree

4 files changed

+70
-259
lines changed

4 files changed

+70
-259
lines changed

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/LineTrackingStringBuffer.cs

Lines changed: 0 additions & 216 deletions
This file was deleted.

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/ParserContext.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,8 @@ public ParserContext(RazorSourceDocument source, RazorParserOptions options)
1818
}
1919

2020
SourceDocument = source;
21-
var chars = new char[source.Length];
22-
source.CopyTo(0, chars, 0, source.Length);
2321

24-
Source = new SeekableTextReader(chars, source.FilePath);
22+
Source = new SeekableTextReader(SourceDocument);
2523
DesignTimeMode = options.DesignTime;
2624
FeatureFlags = options.FeatureFlags;
2725
ParseLeadingDirectives = options.ParseLeadingDirectives;

src/Razor/Microsoft.AspNetCore.Razor.Language/src/Legacy/SeekableTextReader.cs

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,36 @@
33

44
using System;
55
using System.IO;
6+
using System.Text;
7+
using Microsoft.AspNetCore.Razor.Language.Syntax;
68

79
namespace Microsoft.AspNetCore.Razor.Language.Legacy
810
{
911
internal class SeekableTextReader : TextReader, ITextDocument
1012
{
11-
private readonly LineTrackingStringBuffer _buffer;
13+
private readonly RazorSourceDocument _sourceDocument;
1214
private int _position = 0;
1315
private int _current;
1416
private SourceLocation _location;
17+
private (TextSpan Span, int LineIndex) _cachedLineInfo;
1518

16-
public SeekableTextReader(string source, string filePath) : this(source.ToCharArray(), filePath) { }
19+
public SeekableTextReader(string source, string filePath) : this(new StringSourceDocument(source, Encoding.UTF8, new RazorSourceDocumentProperties(filePath, relativePath: null))) { }
1720

18-
public SeekableTextReader(char[] source, string filePath)
21+
public SeekableTextReader(RazorSourceDocument source)
1922
{
2023
if (source == null)
2124
{
2225
throw new ArgumentNullException(nameof(source));
2326
}
2427

25-
_buffer = new LineTrackingStringBuffer(source, filePath);
28+
_sourceDocument = source;
29+
_cachedLineInfo = (new TextSpan(0, _sourceDocument.Lines.GetLineLength(0)), 0);
2630
UpdateState();
2731
}
2832

2933
public SourceLocation Location => _location;
3034

31-
public int Length => _buffer.Length;
35+
public int Length => _sourceDocument.Length;
3236

3337
public int Position
3438
{
@@ -55,22 +59,73 @@ public override int Read()
5559

5660
private void UpdateState()
5761
{
58-
if (_position < _buffer.Length)
62+
if (_cachedLineInfo.Span.Contains(_position))
5963
{
60-
var chr = _buffer.CharAt(_position);
61-
_current = chr.Character;
62-
_location = chr.Location;
64+
_location = new SourceLocation(_sourceDocument.FilePath, _position, _cachedLineInfo.LineIndex, _position - _cachedLineInfo.Span.Start);
65+
_current = _sourceDocument[_location.AbsoluteIndex];
66+
67+
return;
6368
}
64-
else if (_buffer.Length == 0)
69+
70+
if (_position < _sourceDocument.Length)
6571
{
66-
_current = -1;
67-
_location = SourceLocation.Zero;
72+
if (_position >= _cachedLineInfo.Span.End)
73+
{
74+
// Try to avoid the GetLocation call by checking if the next line contains the position
75+
int nextLineIndex = _cachedLineInfo.LineIndex + 1;
76+
int nextLineLength = _sourceDocument.Lines.GetLineLength(nextLineIndex);
77+
TextSpan nextLineSpan = new TextSpan(_cachedLineInfo.Span.End, nextLineLength);
78+
79+
if (nextLineSpan.Contains(_position))
80+
{
81+
_cachedLineInfo = (nextLineSpan, nextLineIndex);
82+
_location = new SourceLocation(_sourceDocument.FilePath, _position, nextLineIndex, _position - nextLineSpan.Start);
83+
_current = _sourceDocument[_location.AbsoluteIndex];
84+
85+
return;
86+
}
87+
}
88+
else
89+
{
90+
// Try to avoid the GetLocation call by checking if the previous line contains the position
91+
int prevLineIndex = _cachedLineInfo.LineIndex - 1;
92+
int prevLineLength = _sourceDocument.Lines.GetLineLength(prevLineIndex);
93+
TextSpan prevLineSpan = new TextSpan(_cachedLineInfo.Span.Start - prevLineLength, prevLineLength);
94+
95+
if (prevLineSpan.Contains(_position))
96+
{
97+
_cachedLineInfo = (prevLineSpan, prevLineIndex);
98+
_location = new SourceLocation(_sourceDocument.FilePath, _position, prevLineIndex, _position - prevLineSpan.Start);
99+
_current = _sourceDocument[_location.AbsoluteIndex];
100+
101+
return;
102+
}
103+
}
104+
105+
// The call to GetLocation is expensive
106+
_location = _sourceDocument.Lines.GetLocation(_position);
107+
108+
int lineLength = _sourceDocument.Lines.GetLineLength(_location.LineIndex);
109+
TextSpan lineSpan = new TextSpan(_position - _location.CharacterIndex, lineLength);
110+
_cachedLineInfo = (lineSpan, _location.LineIndex);
111+
112+
_current = _sourceDocument[_location.AbsoluteIndex];
113+
114+
return;
68115
}
69-
else
116+
117+
if (_sourceDocument.Length == 0)
70118
{
119+
_location = SourceLocation.Zero;
71120
_current = -1;
72-
_location = _buffer.EndLocation;
121+
122+
return;
73123
}
124+
125+
var lineNumber = _sourceDocument.Lines.Count - 1;
126+
_location = new SourceLocation(_sourceDocument.FilePath, Length, lineNumber, _sourceDocument.Lines.GetLineLength(lineNumber));
127+
128+
_current = -1;
74129
}
75130
}
76131
}

src/Razor/Microsoft.AspNetCore.Razor.Language/test/Legacy/LineTrackingStringBufferTest.cs

Lines changed: 0 additions & 26 deletions
This file was deleted.

0 commit comments

Comments
 (0)