Code/Dependencies/StringReader/StringReader.cs
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#nullable enable
using System;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
using System.Threading.Tasks;
namespace WasmBox.Pixie {
// This class implements a text reader that reads from a string.
public class StringReader : System.IO.TextReader {
private string? _s;
private int _pos;
public StringReader( string s ) {
if ( s is null ) {
throw new ArgumentNullException( nameof( s ) );
}
_s = s;
}
public override void Close() {
Dispose( true );
}
protected override void Dispose( bool disposing ) {
_s = null;
_pos = 0;
base.Dispose( disposing );
}
// Returns the next available character without actually reading it from
// the underlying string. The current position of the StringReader is not
// changed by this operation. The returned value is -1 if no further
// characters are available.
//
public override int Peek() {
string? s = _s;
if ( s == null ) {
ThrowObjectDisposedException_ReaderClosed();
}
int pos = _pos;
if ( (uint)pos < (uint)s.Length ) {
return s[pos];
}
return -1;
}
// Reads the next character from the underlying string. The returned value
// is -1 if no further characters are available.
//
public override int Read() {
string? s = _s;
if ( s == null ) {
ThrowObjectDisposedException_ReaderClosed();
}
int pos = _pos;
if ( (uint)pos < (uint)s.Length ) {
_pos++;
return s[pos];
}
return -1;
}
// Reads a block of characters. This method will read up to count
// characters from this StringReader into the buffer character
// array starting at position index. Returns the actual number of
// characters read, or zero if the end of the string is reached.
//
public override int Read( char[] buffer, int index, int count ) {
ArgumentNullException.ThrowIfNull( buffer );
ArgumentOutOfRangeException.ThrowIfNegative( index );
ArgumentOutOfRangeException.ThrowIfNegative( count );
if ( buffer.Length - index < count ) {
throw new ArgumentException( "Invalid offset length" );
}
if ( _s == null ) {
ThrowObjectDisposedException_ReaderClosed();
}
int n = _s.Length - _pos;
if ( n > 0 ) {
if ( n > count ) {
n = count;
}
_s.CopyTo( _pos, buffer, index, n );
_pos += n;
}
return n;
}
public override int Read( Span<char> buffer ) {
if ( GetType() != typeof( StringReader ) ) {
// This overload was added after the Read(char[], ...) overload, and so in case
// a derived type may have overridden it, we need to delegate to it, which the base does.
return base.Read( buffer );
}
string? s = _s;
if ( s == null ) {
ThrowObjectDisposedException_ReaderClosed();
}
int n = s.Length - _pos;
if ( n > 0 ) {
if ( n > buffer.Length ) {
n = buffer.Length;
}
s.AsSpan( _pos, n ).CopyTo( buffer );
_pos += n;
}
return n;
}
public override int ReadBlock( Span<char> buffer ) => Read( buffer );
public override string ReadToEnd() {
string? s = _s;
if ( s == null ) {
ThrowObjectDisposedException_ReaderClosed();
}
int pos = _pos;
_pos = s.Length;
if ( pos != 0 ) {
s = s.Substring( pos );
}
return s;
}
// Reads a line. A line is defined as a sequence of characters followed by
// a carriage return ('\r'), a line feed ('\n'), or a carriage return
// immediately followed by a line feed. The resulting string does not
// contain the terminating carriage return and/or line feed. The returned
// value is null if the end of the underlying string has been reached.
//
public override string? ReadLine() {
string? s = _s;
if ( s == null ) {
ThrowObjectDisposedException_ReaderClosed();
}
int pos = _pos;
if ( (uint)pos >= (uint)s.Length ) {
return null;
}
ReadOnlySpan<char> remaining = s.AsSpan( pos );
int foundLineLength = remaining.IndexOfAny( '\r', '\n' );
if ( foundLineLength >= 0 ) {
string result = s.Substring( pos, foundLineLength );
char ch = remaining[foundLineLength];
pos += foundLineLength + 1;
if ( ch == '\r' ) {
if ( (uint)pos < (uint)s.Length && s[pos] == '\n' ) {
pos++;
}
}
_pos = pos;
return result;
}
else {
string result = s.Substring( pos );
_pos = s.Length;
return result;
}
}
#region Task based Async APIs
public override Task<string?> ReadLineAsync() {
return Task.FromResult( ReadLine() );
}
/// <summary>
/// Reads a line of characters asynchronously from the current string and returns the data as a string.
/// </summary>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A value task that represents the asynchronous read operation. The value of the <c>TResult</c>
/// parameter contains the next line from the string reader, or is <see langword="null" /> if all of the characters have been read.</returns>
/// <exception cref="ArgumentOutOfRangeException">The number of characters in the next line is larger than <see cref="int.MaxValue"/>.</exception>
/// <exception cref="ObjectDisposedException">The string reader has been disposed.</exception>
/// <exception cref="InvalidOperationException">The reader is currently in use by a previous read operation.</exception>
/// <example>
/// The following example shows how to read one line at a time from a string asynchronously.
/// <code lang="C#">
/// using System.Text;
///
/// StringBuilder stringToRead = new();
/// stringToRead.AppendLine("Characters in 1st line to read");
/// stringToRead.AppendLine("and 2nd line");
/// stringToRead.AppendLine("and the end");
///
/// string readText;
/// using CancellationTokenSource tokenSource = new (TimeSpan.FromSeconds(1));
/// using StringReader reader = new (stringToRead.ToString());
/// while ((readText = await reader.ReadLineAsync(tokenSource.Token)) is not null)
/// {
/// Console.WriteLine(readText);
/// }
/// </code>
/// </example>
public override ValueTask<string?> ReadLineAsync( CancellationToken cancellationToken ) =>
cancellationToken.IsCancellationRequested
? ValueTask.FromCanceled<string?>( cancellationToken )
: new ValueTask<string?>( ReadLine() );
public override Task<string> ReadToEndAsync() {
return Task.FromResult( ReadToEnd() );
}
/// <summary>
/// Reads all characters from the current position to the end of the string asynchronously and returns them as a single string.
/// </summary>
/// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
/// <returns>A task that represents the asynchronous read operation. The value of the <c>TResult</c> parameter contains
/// a string with the characters from the current position to the end of the string.</returns>
/// <exception cref="ArgumentOutOfRangeException">The number of characters is larger than <see cref="int.MaxValue"/>.</exception>
/// <exception cref="ObjectDisposedException">The string reader has been disposed.</exception>
/// <exception cref="InvalidOperationException">The reader is currently in use by a previous read operation.</exception>
/// <example>
/// The following example shows how to read an entire string asynchronously.
/// <code lang="C#">
/// using System.Text;
///
/// StringBuilder stringToRead = new();
/// stringToRead.AppendLine("Characters in 1st line to read");
/// stringToRead.AppendLine("and 2nd line");
/// stringToRead.AppendLine("and the end");
///
/// using CancellationTokenSource tokenSource = new (TimeSpan.FromSeconds(1));
/// using StringReader reader = new (stringToRead.ToString());
/// var readText = await reader.ReadToEndAsync(tokenSource.Token);
/// Console.WriteLine(readText);
/// </code>
/// </example>
public override Task<string> ReadToEndAsync( CancellationToken cancellationToken ) =>
cancellationToken.IsCancellationRequested
? Task.FromCanceled<string>( cancellationToken )
: Task.FromResult( ReadToEnd() );
public override Task<int> ReadBlockAsync( char[] buffer, int index, int count ) {
ArgumentNullException.ThrowIfNull( buffer );
ArgumentOutOfRangeException.ThrowIfNegative( index );
ArgumentOutOfRangeException.ThrowIfNegative( count );
if ( buffer.Length - index < count ) {
throw new ArgumentException( "Invalid offset length" );
}
return Task.FromResult( ReadBlock( buffer, index, count ) );
}
// public override ValueTask<int> ReadBlockAsync( Memory<char> buffer, CancellationToken cancellationToken = default ) =>
// cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled<int>( cancellationToken ) :
// new ValueTask<int>( ReadBlock( buffer.Span ) );
public override Task<int> ReadAsync( char[] buffer, int index, int count ) {
ArgumentNullException.ThrowIfNull( buffer );
ArgumentOutOfRangeException.ThrowIfNegative( index );
ArgumentOutOfRangeException.ThrowIfNegative( count );
if ( buffer.Length - index < count ) {
throw new ArgumentException( "Invalid offset length" );
}
return Task.FromResult( Read( buffer, index, count ) );
}
// public override ValueTask<int> ReadAsync( Memory<char> buffer, CancellationToken cancellationToken = default ) =>
// cancellationToken.IsCancellationRequested ? ValueTask.FromCanceled<int>( cancellationToken ) :
// new ValueTask<int>( Read( buffer.Span ) );
#endregion
[DoesNotReturn]
private static void ThrowObjectDisposedException_ReaderClosed() {
throw new ObjectDisposedException( null, "ObjectDisposed_ReaderClosed" );
}
}
}