Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
96 commits
Select commit Hold shift + click to select a range
40ad7f0
Move field into lexer
CyrusNajmabadi Jun 30, 2025
0ea5fbc
more work
CyrusNajmabadi Jun 30, 2025
a472f31
Update cache
CyrusNajmabadi Jun 30, 2025
3d1fc7f
All done
CyrusNajmabadi Jun 30, 2025
fd45aec
Remove
CyrusNajmabadi Jun 30, 2025
16573f2
Doc
CyrusNajmabadi Jun 30, 2025
e1eb1b4
Fix
CyrusNajmabadi Jun 30, 2025
362c1cb
Fix
CyrusNajmabadi Jun 30, 2025
d8a8748
Make sure we disable tracing
CyrusNajmabadi Jun 30, 2025
712f2f5
Fix test
CyrusNajmabadi Jun 30, 2025
a852cfd
Fixup
CyrusNajmabadi Jun 30, 2025
8f278a3
Simplify
CyrusNajmabadi Jun 30, 2025
15fbaec
Add tracing
CyrusNajmabadi Jun 30, 2025
daf9f80
Add tracing
CyrusNajmabadi Jun 30, 2025
ebf24ab
Add docs
CyrusNajmabadi Jun 30, 2025
54e6e27
remove
CyrusNajmabadi Jun 30, 2025
284cf83
remove
CyrusNajmabadi Jun 30, 2025
e9a740c
Fixup test
CyrusNajmabadi Jun 30, 2025
f6817ee
Simplify
CyrusNajmabadi Jun 30, 2025
1f0ec4a
Check both ends
CyrusNajmabadi Jun 30, 2025
e2885e4
Docs
CyrusNajmabadi Jun 30, 2025
7fc3b85
cleanup
CyrusNajmabadi Jun 30, 2025
cedf899
Inline method
CyrusNajmabadi Jun 30, 2025
a127c8f
Merge branch 'main' into lexerField
CyrusNajmabadi Jul 1, 2025
df0944b
Merge remote-tracking branch 'upstream/main' into lexerField
CyrusNajmabadi Jul 2, 2025
1441828
Delete
CyrusNajmabadi Jul 2, 2025
799edf7
Merge remote-tracking branch 'upstream/main' into lexerField
CyrusNajmabadi Jul 2, 2025
337ae17
Simplify
CyrusNajmabadi Jul 2, 2025
31d6b9c
In progress
CyrusNajmabadi Jul 2, 2025
596a146
Remove unused function
CyrusNajmabadi Jul 2, 2025
40a3098
Remove unused function
CyrusNajmabadi Jul 2, 2025
ad0183f
Merge branch 'removeFunction' into lexerField
CyrusNajmabadi Jul 2, 2025
b60a263
Merge branch 'useSpan' into lexerField
CyrusNajmabadi Jul 2, 2025
6a66e2a
Use span
CyrusNajmabadi Jul 2, 2025
522713c
Simplify
CyrusNajmabadi Jul 2, 2025
68b59a4
Fixup
CyrusNajmabadi Jul 2, 2025
7de725f
Simplify
CyrusNajmabadi Jul 2, 2025
d88102e
Updates
CyrusNajmabadi Jul 2, 2025
d964f29
Make private
CyrusNajmabadi Jul 2, 2025
3857e2d
In progress
CyrusNajmabadi Jul 2, 2025
dcd95b1
In progress
CyrusNajmabadi Jul 2, 2025
28ee138
In progress
CyrusNajmabadi Jul 2, 2025
8686e17
Remove field
CyrusNajmabadi Jul 2, 2025
cb19c83
Remove field
CyrusNajmabadi Jul 2, 2025
7eb1016
REmove offset
CyrusNajmabadi Jul 2, 2025
64984e5
remove property
CyrusNajmabadi Jul 2, 2025
68d625b
remove property
CyrusNajmabadi Jul 2, 2025
f577a9f
Docs
CyrusNajmabadi Jul 2, 2025
46a9aaa
Add helper
CyrusNajmabadi Jul 2, 2025
781eb9a
Building
CyrusNajmabadi Jul 2, 2025
583b578
Renames
CyrusNajmabadi Jul 2, 2025
8be8696
Add docs
CyrusNajmabadi Jul 2, 2025
45769cb
Test accessors
CyrusNajmabadi Jul 2, 2025
41dd1f4
Test accessors
CyrusNajmabadi Jul 2, 2025
4828ded
Fix
CyrusNajmabadi Jul 2, 2025
aaacaf2
Revert
CyrusNajmabadi Jul 2, 2025
8f2c2ae
fix
CyrusNajmabadi Jul 2, 2025
2c8c6c7
Cleanup
CyrusNajmabadi Jul 2, 2025
7dbd1de
Merge branch 'main' into lexerField
CyrusNajmabadi Jul 2, 2025
a1dba56
Update src/Compilers/CSharp/csc/Program.cs
CyrusNajmabadi Jul 2, 2025
7a2ab71
remove
CyrusNajmabadi Jul 2, 2025
c66a5c3
Merge branch 'lexerField' of https://github.com/CyrusNajmabadi/roslyn…
CyrusNajmabadi Jul 2, 2025
9b4941a
Make struct
CyrusNajmabadi Jul 2, 2025
b05a58c
Update src/Compilers/CSharp/csc/Program.cs
CyrusNajmabadi Jul 2, 2025
1f907ab
REvert
CyrusNajmabadi Jul 2, 2025
3c87760
Merge branch 'lexerField' of https://github.com/CyrusNajmabadi/roslyn…
CyrusNajmabadi Jul 2, 2025
362a47c
Remove from release
CyrusNajmabadi Jul 2, 2025
b69e574
Make readonly
CyrusNajmabadi Jul 2, 2025
7219354
Make readonly
CyrusNajmabadi Jul 2, 2025
91959bd
make not readonly
CyrusNajmabadi Jul 2, 2025
aa6e641
Fix
CyrusNajmabadi Jul 2, 2025
b4bc7a4
Fix tests
CyrusNajmabadi Jul 2, 2025
0491e62
Merge remote-tracking branch 'upstream/main' into lexerField
CyrusNajmabadi Jul 3, 2025
e417a13
ifdef
CyrusNajmabadi Jul 3, 2025
ae6d7a3
No disposable
CyrusNajmabadi Jul 3, 2025
9be1995
Docs
CyrusNajmabadi Jul 3, 2025
0620cbe
share
CyrusNajmabadi Jul 3, 2025
51cef33
Merge remote-tracking branch 'upstream/main' into lexerField
CyrusNajmabadi Jul 3, 2025
38772d9
Merge
CyrusNajmabadi Jul 3, 2025
39f10e2
Simplify
CyrusNajmabadi Jul 3, 2025
a42ff59
Update src/Compilers/CSharp/Test/Syntax/Resources.resx
CyrusNajmabadi Jul 3, 2025
2e41f81
Merge branch 'main' into lexerField
CyrusNajmabadi Jul 9, 2025
64a3105
Merge branch 'main' into lexerField
CyrusNajmabadi Jul 17, 2025
37ce151
Docs
CyrusNajmabadi Jul 17, 2025
c47db70
Docs
CyrusNajmabadi Jul 17, 2025
61d4fd5
Merge remote-tracking branch 'upstream/main' into lexerField
CyrusNajmabadi Jul 17, 2025
4256939
Tweak wording
CyrusNajmabadi Jul 17, 2025
17e1edb
Merge branch 'main' into lexerField
CyrusNajmabadi Jul 21, 2025
de4388f
Merge branch 'main' into lexerField
CyrusNajmabadi Jul 23, 2025
a21a493
Apply suggestions from code review
CyrusNajmabadi Jul 23, 2025
061f4e2
Merge remote-tracking branch 'upstream/main' into lexerField
CyrusNajmabadi Jul 23, 2025
b301919
Don't return invalid span
CyrusNajmabadi Jul 23, 2025
636a56b
Add assertion
CyrusNajmabadi Jul 23, 2025
d8367f7
Remove 'else'
CyrusNajmabadi Jul 23, 2025
82ee10a
Use clamped position
CyrusNajmabadi Jul 23, 2025
fa199ec
Check both sides
CyrusNajmabadi Jul 23, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 12 additions & 4 deletions src/Compilers/CSharp/Portable/Parser/AbstractLexer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@

using System;
using System.Collections.Generic;
using Microsoft.CodeAnalysis.CSharp.Symbols;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Text;

namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
Expand All @@ -15,6 +13,7 @@ internal class AbstractLexer : IDisposable
{
internal readonly SlidingTextWindow TextWindow;
private List<SyntaxDiagnosticInfo>? _errors;
protected int LexemeStartPosition;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

moved to lexer-layer from SlidingTextWindow


protected AbstractLexer(SourceText text)
{
Expand All @@ -28,7 +27,7 @@ public virtual void Dispose()

protected void Start()
{
TextWindow.Start();
LexemeStartPosition = this.TextWindow.Position;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the text window is now blissfully unaware of what the lexer is doing with lexemes.

_errors = null;
}

Expand Down Expand Up @@ -131,9 +130,18 @@ protected XmlSyntaxDiagnosticInfo MakeError(int position, int width, XmlParseErr

private int GetLexemeOffsetFromPosition(int position)
{
return position >= TextWindow.LexemeStartPosition ? position - TextWindow.LexemeStartPosition : position;
return position >= LexemeStartPosition ? position - LexemeStartPosition : position;
}

protected string GetNonInternedLexemeText()
=> TextWindow.GetText(LexemeStartPosition, intern: false);

protected string GetInternedLexemeText()
=> TextWindow.GetText(LexemeStartPosition, intern: true);
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these .GetText helpers on the TextWindow were used all over the place. MOved to two simple helpers on Lexer for getting either an interned or non-interned chunk of text for the current lexeme.


protected int CurrentLexemeWidth
=> this.TextWindow.Position - LexemeStartPosition;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was previously exposed as the (imo) confusingly named SlidingTextWindow.Width.

I tried to ensure that helpers related to lexemes used that term in their name to keep it clear what 'width/count/etc.' things are referring to.


protected static SyntaxDiagnosticInfo MakeError(ErrorCode code)
{
return new SyntaxDiagnosticInfo(code);
Expand Down
140 changes: 66 additions & 74 deletions src/Compilers/CSharp/Portable/Parser/Lexer.cs

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions src/Compilers/CSharp/Portable/Parser/LexerCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
// #define COLLECT_STATS

using System;
using System.Linq;
using Microsoft.CodeAnalysis.PooledObjects;
using Microsoft.CodeAnalysis.Syntax.InternalSyntax;
using Roslyn.Utilities;
using static Microsoft.CodeAnalysis.CSharp.NullableWalker;

namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
{
Expand Down Expand Up @@ -200,6 +202,35 @@ internal SyntaxTrivia LookupTrivia<TArg>(
return value;
}


internal SyntaxTrivia LookupWhitespaceTrivia(
SlidingTextWindow textWindow,
int lexemeStartPosition,
int hashCode)
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

easier to just read as the new function. There was only ever one caller ofr LookupTrivia, and it always used the same generic arg.

{
var lexemeWidth = textWindow.Position - lexemeStartPosition;
var textBuffer = textWindow.CharacterWindow;

// If the whitespace is entirely within the character window, grab from that and cache.
var lexemeEndPosition = lexemeStartPosition + lexemeWidth;
if (lexemeStartPosition >= textWindow.CharacterWindowStartPositionInText && lexemeEndPosition <= textWindow.CharacterWindowEndPositionInText)
{
var keyStart = lexemeStartPosition - textWindow.CharacterWindowStartPositionInText;
var value = TriviaMap.FindItem(textBuffer, keyStart, lexemeWidth, hashCode);

if (value == null)
{
value = SyntaxFactory.Whitespace(textWindow.GetText(lexemeStartPosition, intern: true));
TriviaMap.AddItem(textBuffer, keyStart, lexemeWidth, hashCode, value);
}

return value;
}

// Otherwise, if it's outside of the window, just grab from the underlying text.
return SyntaxFactory.Whitespace(textWindow.GetText(lexemeStartPosition, intern: true));
}

// TODO: remove this when done tweaking this cache.
#if COLLECT_STATS
private static int hits = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ private void ScanRawStringLiteral(ref TokenInfo info, bool inDirective)
// trusting the contents.
if (this.HasErrors)
{
var afterStartDelimiter = TextWindow.LexemeStartPosition + startingQuoteCount;
var afterStartDelimiter = this.LexemeStartPosition + startingQuoteCount;
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reference to textWindow.LexemeStartPosition were mechanically replaced with this.LexemeStartPosition

var valueLength = TextWindow.Position - afterStartDelimiter;

info.StringValue = TextWindow.GetText(
Expand Down Expand Up @@ -120,7 +120,7 @@ private void ScanRawStringLiteral(ref TokenInfo info, bool inDirective)
};
}

info.Text = TextWindow.GetText(intern: true);
info.Text = this.GetInternedLexemeText();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

References to TextWindow.GetText(intern: true) and TextWindow.GetText(intern: false) were mechanically updated to this.GetInternedLexemeText() and this.GetNonInternedLexemeText() mechanically.

}

private void ScanSingleLineRawStringLiteral(ref TokenInfo info, int startingQuoteCount)
Expand Down Expand Up @@ -169,7 +169,7 @@ private void ScanSingleLineRawStringLiteral(ref TokenInfo info, int startingQuot
}

// We have enough quotes to finish this string at this point.
var afterStartDelimiter = TextWindow.LexemeStartPosition + startingQuoteCount;
var afterStartDelimiter = this.LexemeStartPosition + startingQuoteCount;
var valueLength = beforeEndDelimiter - afterStartDelimiter;

info.StringValue = TextWindow.GetText(
Expand Down
10 changes: 5 additions & 5 deletions src/Compilers/CSharp/Portable/Parser/Lexer_StringLiteral.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ private void ScanStringLiteral(ref TokenInfo info, bool inDirective)
//String and character literals can contain any Unicode character. They are not limited
//to valid UTF-16 characters. So if we get the SlidingTextWindow's sentinel value,
//double check that it was not real user-code contents. This will be rare.
Debug.Assert(TextWindow.Width > 0);
Debug.Assert(this.CurrentLexemeWidth > 0);
this.AddError(ErrorCode.ERR_NewlineInConst);
break;
}
Expand All @@ -73,7 +73,7 @@ private void ScanStringLiteral(ref TokenInfo info, bool inDirective)

if (quoteCharacter == '\'')
{
info.Text = TextWindow.GetText(intern: true);
info.Text = this.GetInternedLexemeText();
info.Kind = SyntaxKind.CharacterLiteralToken;
if (_builder.Length != 1)
{
Expand Down Expand Up @@ -102,7 +102,7 @@ private void ScanStringLiteral(ref TokenInfo info, bool inDirective)
info.Kind = SyntaxKind.StringLiteralToken;
}

info.Text = TextWindow.GetText(intern: true);
info.Text = this.GetInternedLexemeText();

if (_builder.Length > 0)
{
Expand Down Expand Up @@ -247,7 +247,7 @@ private void ScanVerbatimStringLiteral(ref TokenInfo info)
info.Kind = SyntaxKind.StringLiteralToken;
}

info.Text = TextWindow.GetText(intern: false);
info.Text = this.GetNonInternedLexemeText();
info.StringValue = _builder.ToString();
}

Expand Down Expand Up @@ -290,7 +290,7 @@ internal void ScanInterpolatedStringLiteralTop(
subScanner.ScanInterpolatedStringLiteralTop(out kind, out openQuoteRange, interpolations, out closeQuoteRange);
error = subScanner.Error;
info.Kind = SyntaxKind.InterpolatedStringToken;
info.Text = TextWindow.GetText(intern: false);
info.Text = this.GetNonInternedLexemeText();
}

/// <summary>
Expand Down
32 changes: 19 additions & 13 deletions src/Compilers/CSharp/Portable/Parser/QuickScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

using System;
using System.Diagnostics;
using System.Linq;
using Roslyn.Utilities;

namespace Microsoft.CodeAnalysis.CSharp.Syntax.InternalSyntax
Expand Down Expand Up @@ -193,19 +194,22 @@ private enum CharFlags : byte
{
this.Start();
var state = QuickScanState.Initial;
int i = TextWindow.Offset;
int n = TextWindow.CharacterWindowCount;
n = Math.Min(n, i + MaxCachedTokenSize);

var charWindow = TextWindow.CharacterWindow;
var startIndex = TextWindow.Position - TextWindow.CharacterWindowStartPositionInText;
var currentIndex = startIndex;
var n = charWindow.Length;

n = Math.Min(n, currentIndex + MaxCachedTokenSize);

int hashCode = Hash.FnvOffsetBias;

//localize frequently accessed fields
var charWindow = TextWindow.CharacterWindow;
var charPropLength = CharProperties.Length;

for (; i < n; i++)
for (; currentIndex < n; currentIndex++)
{
char c = charWindow[i];
char c = charWindow[currentIndex];
int uc = unchecked((int)c);

var flags = uc < charPropLength ? (CharFlags)CharProperties[uc] : CharFlags.Complex;
Expand All @@ -228,34 +232,36 @@ private enum CharFlags : byte
state = QuickScanState.Bad; // ran out of characters in window
exitWhile:

TextWindow.AdvanceChar(i - TextWindow.Offset);
// Note: it is fine to change this position here and then read from charWindow below. AdvanceChar guarantees
// that it will not actually mutate that state.
TextWindow.AdvanceChar(currentIndex - TextWindow.Offset);
Debug.Assert(state == QuickScanState.Bad || state == QuickScanState.Done, "can only exit with Bad or Done");

if (state == QuickScanState.Done)
{
// this is a good token!
var token = _cache.LookupToken(
TextWindow.CharacterWindow,
TextWindow.LexemeRelativeStart,
i - TextWindow.LexemeRelativeStart,
charWindow,
keyStart: startIndex,
keyLength: currentIndex - startIndex,
hashCode,
CreateQuickToken,
this);
return token;
}
else
{
TextWindow.Reset(TextWindow.LexemeStartPosition);
Copy link
Member Author

@CyrusNajmabadi CyrusNajmabadi Jul 2, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to reset anything. the text window didn't move. this function only ever reads directly from the backing char array.

TextWindow.Reset(this.LexemeStartPosition);
return null;
}
}

private static SyntaxToken CreateQuickToken(Lexer lexer)
{
#if DEBUG
var quickWidth = lexer.TextWindow.Width;
var quickWidth = lexer.CurrentLexemeWidth;
#endif
lexer.TextWindow.Reset(lexer.TextWindow.LexemeStartPosition);
lexer.TextWindow.Reset(lexer.LexemeStartPosition);
var token = lexer.LexSyntaxToken();
#if DEBUG
Debug.Assert(quickWidth == token.FullWidth);
Expand Down
77 changes: 13 additions & 64 deletions src/Compilers/CSharp/Portable/Parser/SlidingTextWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,6 @@ internal sealed class SlidingTextWindow : IDisposable
private char[] _characterWindow; // Moveable window of chars from source text
private int _characterWindowCount; // # of valid characters in chars buffer
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

primary goal was to simplify the heck out of this goop. Instead of having lots of things to keep in sync, we simplify our lives by using well known types. Specifically, ArraySegment encapsulates the window and its count. And there's no need for a separate offset/basis.


private int _lexemeStart; // Start of current lexeme relative to the window start.

// Example for the above variables:
// The text starts at 0.
// The window onto the text starts at basis.
Expand All @@ -64,7 +62,6 @@ public SlidingTextWindow(SourceText text)
_textEnd = text.Length;
_strings = StringTable.GetInstance();
_characterWindow = s_windowPool.Allocate();
_lexemeStart = 0;
}

public void Dispose()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

naming this like all our other freeable-types. and explicitly made non-IDisposable. This shouldn't be boxed into that type.

Expand Down Expand Up @@ -112,17 +109,6 @@ public char[] CharacterWindow
}
}

/// <summary>
/// Returns the start of the current lexeme relative to the window start.
/// </summary>
public int LexemeRelativeStart
{
get
{
return _lexemeStart;
}
}

/// <summary>
/// Number of characters in the character window.
/// </summary>
Expand All @@ -135,35 +121,19 @@ public int CharacterWindowCount
}

/// <summary>
/// The absolute position of the start of the current lexeme in the given
/// SourceText.
/// </summary>
public int LexemeStartPosition
{
get
{
return _basis + _lexemeStart;
}
}

/// <summary>
/// The number of characters in the current lexeme.
/// Offset of the <see cref="_characterWindow"/> within <see cref="_text"/>. In other words, if this is 2048, then that means
/// it represents the chunk of characters starting at position 2048 in the source text. <see cref="CharacterWindowCount"/> represents
/// how large the chunk is. Characters <c>[0, CharacterWindowCount)</c> are valid characters within the window, and represents
/// the chunk <c>[CharacterWindowStartPositionInText, CharacterWindowEndPositionInText)</c> in <see cref="_text"/>.
/// </summary>
public int Width
{
get
{
return _offset - _lexemeStart;
}
}
public int CharacterWindowStartPositionInText => _basis;

/// <summary>
/// Start parsing a new lexeme.
/// Similar to <see cref="CharacterWindowStartPositionInText"/>, except this represents the index (exclusive) of the first character
/// that <see cref="_characterWindow"/> encompases in <see cref="_text"/>. This is equal to <see cref="CharacterWindowStartPositionInText"/>
/// + <see cref="CharacterWindowCount"/>.
/// </summary>
public void Start()
{
_lexemeStart = _offset;
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need "start" teh text window. it does not care about lexemes. it just cares about caching the most likely segment of chars needed by the lexer to read from.

public int CharacterWindowEndPositionInText => this.CharacterWindowStartPositionInText + this.CharacterWindowCount;

public void Reset(int position)
{
Expand All @@ -183,7 +153,6 @@ public void Reset(int position)
_text.CopyTo(position, _characterWindow, 0, amountToRead);
}

_lexemeStart = 0;
_offset = 0;
_basis = position;
_characterWindowCount = amountToRead;
Expand All @@ -199,21 +168,6 @@ private bool MoreChars()
return false;
}

// if lexeme scanning is sufficiently into the char buffer,
// then refocus the window onto the lexeme
if (_lexemeStart > (_characterWindowCount / 4))
{
Array.Copy(_characterWindow,
_lexemeStart,
_characterWindow,
0,
_characterWindowCount - _lexemeStart);
_characterWindowCount -= _lexemeStart;
_offset -= _lexemeStart;
_basis += _lexemeStart;
_lexemeStart = 0;
}

if (_characterWindowCount >= _characterWindow.Length)
{
// grow char array, since we need more contiguous space
Expand Down Expand Up @@ -272,8 +226,8 @@ public bool TryAdvance(char c)
}

/// <summary>
/// Advance the current position by n. No guarantee that this position
/// is valid.
/// Advance the current position by n. No guarantee that this position is valid. This will <em>not</em> change the character window
/// in any way. Specifically, it will not create a new character window, nor will it change the contents of the current window.
/// </summary>
public void AdvanceChar(int n)
{
Expand Down Expand Up @@ -419,14 +373,9 @@ public string Intern(char[] array, int start, int length)
return _strings.Add(array, start, length);
}

public string GetInternedText()
{
return this.Intern(_characterWindow, _lexemeStart, this.Width);
}

public string GetText(bool intern)
public string GetText(int startPosition, bool intern)
{
return this.GetText(this.LexemeStartPosition, this.Width, intern);
return this.GetText(startPosition, this.Position - startPosition, intern);
}

public string GetText(int position, int length, bool intern)
Expand Down