202 lines
6.4 KiB
C#
202 lines
6.4 KiB
C#
// Copyright (c) .NET Foundation. All rights reserved.
|
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
|
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using Microsoft.AspNetCore.Razor.Utils;
|
|
|
|
namespace Microsoft.AspNetCore.Razor.Text
|
|
{
|
|
public class BufferingTextReader : LookaheadTextReader
|
|
{
|
|
private Stack<BacktrackContext> _backtrackStack = new Stack<BacktrackContext>();
|
|
private int _currentBufferPosition;
|
|
|
|
private int _currentCharacter;
|
|
private SourceLocationTracker _locationTracker;
|
|
|
|
public BufferingTextReader(TextReader source)
|
|
{
|
|
if (source == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(source));
|
|
}
|
|
|
|
InnerReader = source;
|
|
_locationTracker = new SourceLocationTracker();
|
|
|
|
UpdateCurrentCharacter();
|
|
}
|
|
|
|
internal StringBuilder Buffer { get; set; }
|
|
internal bool Buffering { get; set; }
|
|
internal TextReader InnerReader { get; private set; }
|
|
|
|
public override SourceLocation CurrentLocation
|
|
{
|
|
get { return _locationTracker.CurrentLocation; }
|
|
}
|
|
|
|
protected virtual int CurrentCharacter
|
|
{
|
|
get { return _currentCharacter; }
|
|
}
|
|
|
|
public override int Read()
|
|
{
|
|
var ch = CurrentCharacter;
|
|
NextCharacter();
|
|
return ch;
|
|
}
|
|
|
|
// TODO: Optimize Read(char[],int,int) to copy direct from the buffer where possible
|
|
|
|
public override int Peek()
|
|
{
|
|
return CurrentCharacter;
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
InnerReader.Dispose();
|
|
}
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
public override IDisposable BeginLookahead()
|
|
{
|
|
// Is this our first lookahead?
|
|
if (Buffer == null)
|
|
{
|
|
// Yes, setup the backtrack buffer
|
|
Buffer = new StringBuilder();
|
|
}
|
|
|
|
if (!Buffering)
|
|
{
|
|
// We're not already buffering, so we need to expand the buffer to hold the first character
|
|
ExpandBuffer();
|
|
Buffering = true;
|
|
}
|
|
|
|
// Mark the position to return to when we backtrack
|
|
// Use the closures and the "using" statement rather than an explicit stack
|
|
var context = new BacktrackContext()
|
|
{
|
|
BufferIndex = _currentBufferPosition,
|
|
Location = CurrentLocation
|
|
};
|
|
_backtrackStack.Push(context);
|
|
return new DisposableAction(() =>
|
|
{
|
|
EndLookahead(context);
|
|
});
|
|
}
|
|
|
|
// REVIEW: This really doesn't sound like the best name for this...
|
|
public override void CancelBacktrack()
|
|
{
|
|
if (_backtrackStack.Count == 0)
|
|
{
|
|
throw new InvalidOperationException(RazorResources.CancelBacktrack_Must_Be_Called_Within_Lookahead);
|
|
}
|
|
// Just pop the current backtrack context so that when the lookahead ends, it won't be backtracked
|
|
_backtrackStack.Pop();
|
|
}
|
|
|
|
private void EndLookahead(BacktrackContext context)
|
|
{
|
|
// If the specified context is not the one on the stack, it was popped by a call to DoNotBacktrack
|
|
if (_backtrackStack.Count > 0 && ReferenceEquals(_backtrackStack.Peek(), context))
|
|
{
|
|
_backtrackStack.Pop();
|
|
_currentBufferPosition = context.BufferIndex;
|
|
_locationTracker.CurrentLocation = context.Location;
|
|
|
|
UpdateCurrentCharacter();
|
|
}
|
|
}
|
|
|
|
protected virtual void NextCharacter()
|
|
{
|
|
var prevChar = CurrentCharacter;
|
|
if (prevChar == -1)
|
|
{
|
|
return; // We're at the end of the source
|
|
}
|
|
|
|
if (Buffering)
|
|
{
|
|
if (_currentBufferPosition >= Buffer.Length - 1)
|
|
{
|
|
// If there are no more lookaheads (thus no need to continue with the buffer) we can just clean up the buffer
|
|
if (_backtrackStack.Count == 0)
|
|
{
|
|
// Reset the buffer
|
|
Buffer.Length = 0;
|
|
_currentBufferPosition = 0;
|
|
Buffering = false;
|
|
}
|
|
else if (!ExpandBuffer())
|
|
{
|
|
// Failed to expand the buffer, because we're at the end of the source
|
|
_currentBufferPosition = Buffer.Length; // Force the position past the end of the buffer
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Not at the end yet, just advance the buffer pointer
|
|
_currentBufferPosition++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Just act like normal
|
|
InnerReader.Read(); // Don't care about the return value, Peek() is used to get characters from the source
|
|
}
|
|
|
|
UpdateCurrentCharacter();
|
|
_locationTracker.UpdateLocation((char)prevChar, (char)CurrentCharacter);
|
|
}
|
|
|
|
protected bool ExpandBuffer()
|
|
{
|
|
// Pull another character into the buffer and update the position
|
|
var ch = InnerReader.Read();
|
|
|
|
// Only append the character to the buffer if there actually is one
|
|
if (ch != -1)
|
|
{
|
|
Buffer.Append((char)ch);
|
|
_currentBufferPosition = Buffer.Length - 1;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
private void UpdateCurrentCharacter()
|
|
{
|
|
if (Buffering && _currentBufferPosition < Buffer.Length)
|
|
{
|
|
// Read from the buffer
|
|
_currentCharacter = (int)Buffer[_currentBufferPosition];
|
|
}
|
|
else
|
|
{
|
|
// No buffer? Peek from the source
|
|
_currentCharacter = InnerReader.Peek();
|
|
}
|
|
}
|
|
|
|
private class BacktrackContext
|
|
{
|
|
public int BufferIndex { get; set; }
|
|
public SourceLocation Location { get; set; }
|
|
}
|
|
}
|
|
}
|