2019-08-23 11:56:54 -07:00
// This file contains portions of code released by Microsoft under the MIT license as part
// of an open-sourcing initiative in 2014 of the C# core libraries.
// The original source was submitted to https://github.com/Microsoft/referencesource
using System.Diagnostics.Contracts ;
using System.Diagnostics ;
using System.Text ;
using System.Threading.Tasks ;
using System.Threading ;
2020-04-29 06:40:03 -07:00
using System.Collections ;
2019-08-23 11:56:54 -07:00
namespace System.IO
{
class StreamReader
{
private const int32 DefaultFileStreamBufferSize = 4096 ;
private const int32 MinBufferSize = 128 ;
Stream mStream ~ if ( mOwnsStream ) delete _ ;
public bool mOwnsStream ;
int mCharLen ;
int mCharPos ;
private int mBytePos ;
// Record the number of valid bytes in the byteBuffer, for a few checks.
private int mByteLen ;
// We will support looking for byte order marks in the stream and trying
// to decide what the encoding might be from the byte order marks, IF they
// exist. But that's all we'll do.
private bool mDetectEncoding ;
// Whether we must still check for the encoding's given preamble at the
// beginning of this file.
private bool mCheckPreamble ;
// Whether the stream is most likely not going to give us back as much
// data as we want the next time we call it. We must do the computation
// before we do any byte order mark handling and save the result. Note
// that we need this to allow users to handle streams used for an
// interactive protocol, where they block waiting for the remote end
// to send a response, like logging in on a Unix machine.
private bool mIsBlocked ;
private uint8 [ ] mPreamble ~ delete _ ; // Encoding's preamble, which identifies this encoding.
private uint8 [ ] mByteBuffer ;
private char8 [ ] mCharBuffer ;
private bool mOwnsBuffers ;
private int32 mMaxCharsPerBuffer ;
private Encoding mEncoding ;
2023-04-17 15:16:19 -07:00
private bool mPendingNewlineCheck ;
2019-08-23 11:56:54 -07:00
public Stream BaseStream
{
get
{
return mStream ;
}
}
public this ( )
{
}
public ~ this ( )
{
if ( mOwnsBuffers )
{
delete mByteBuffer ;
delete mCharBuffer ;
}
}
public Encoding CurrentEncoding
{
get
{
return mEncoding ;
}
}
public LineReader Lines
{
get
{
return LineReader ( this ) ;
}
}
[AllowAppend]
public this ( Stream stream , Encoding encoding , bool detectEncodingFromByteOrderMarks , int32 bufferSize , bool ownsSteam = false )
{
int32 useBufferSize = bufferSize ;
if ( useBufferSize < MinBufferSize ) useBufferSize = MinBufferSize ;
let maxCharsPerBuffer = ( encoding ! = null ) ? encoding . GetMaxCharCount ( useBufferSize ) : bufferSize ;
let byteBuffer = append uint8 [ useBufferSize ] ;
let charBuffer = append char8 [ maxCharsPerBuffer ] ;
/ * let byteBuffer = new uint8 [ useBufferSize ] ;
let charBuffer = new char8 [ maxCharsPerBuffer ] ;
mOwnsBuffers = true ; * /
mMaxCharsPerBuffer = ( . ) maxCharsPerBuffer ;
mByteBuffer = byteBuffer ;
mCharBuffer = charBuffer ;
mOwnsStream = ownsSteam ;
Init ( stream , encoding , detectEncodingFromByteOrderMarks , bufferSize ) ;
}
[AllowAppend]
public this ( Stream stream ) : this ( stream , . UTF8 , false , 4096 )
{
}
int GetMaxCharCount ( int32 byteCount )
{
if ( mEncoding = = null )
return byteCount ;
// UTF-8 to UTF-8
return mEncoding . GetMaxCharCount ( byteCount ) ;
}
void Init ( Stream stream , Encoding encoding , bool detectEncodingFromByteOrderMarks , int32 bufferSize )
{
mStream = stream ;
this . mEncoding = encoding ;
//decoder = encoding.GetDecoder();
if ( mByteBuffer = = null )
{
int32 useBufferSize = bufferSize ;
if ( useBufferSize < MinBufferSize ) useBufferSize = MinBufferSize ;
mMaxCharsPerBuffer = ( . ) GetMaxCharCount ( useBufferSize ) ;
mByteBuffer = new uint8 [ useBufferSize ] ;
mCharBuffer = new char8 [ mMaxCharsPerBuffer ] ;
mOwnsBuffers = true ;
}
mByteLen = 0 ;
mBytePos = 0 ;
mDetectEncoding = detectEncodingFromByteOrderMarks ;
//mPreamble = encoding.GetPreamble();
mCheckPreamble = false ; //(mPreamble.Length > 0);
mIsBlocked = false ;
}
public virtual void Dispose ( )
{
delete mStream ;
mStream = null ;
}
public bool EndOfStream
{
get
{
if ( mCharPos < mCharLen )
return false ;
// This may block on pipes!
int numRead = ReadBuffer ( ) ;
return numRead = = 0 ;
}
}
2023-04-17 15:16:19 -07:00
public bool CanReadNow
{
get
{
if ( mCharPos < mCharLen )
return true ;
if ( ReadBuffer ( true ) case . Ok ( let count ) )
return count > 0 ;
return false ;
}
}
2019-08-23 11:56:54 -07:00
public Result < void , FileOpenError > Open ( StringView fileName )
{
Contract . Assert ( mStream = = null ) ;
var fileStream = new FileStream ( ) ;
Encoding encoding = null ;
Init ( fileStream , encoding , true , DefaultFileStreamBufferSize ) ;
mOwnsStream = true ;
if ( fileStream . Open ( fileName , FileMode . Open , FileAccess . Read , FileShare . ReadWrite ) case . Err ( let err ) )
return . Err ( err ) ;
return . Ok ;
}
2022-01-01 21:25:52 -03:00
public Result < char8 > Peek ( )
{
if ( mStream = = null )
return . Err ;
if ( mCharPos = = mCharLen )
{
if ( Try ! ( ReadBuffer ( ) ) = = 0 ) return . Err ;
}
2022-01-07 18:23:50 -03:00
return mCharBuffer [ mCharPos ] ;
2022-01-01 21:25:52 -03:00
}
2019-08-23 11:56:54 -07:00
public Task < String > ReadLineAsync ( )
{
// If we have been inherited into a subclass, the following implementation could be incorrect
// since it does not call through to Read() which a subclass might have overriden.
// To be safe we will only use this implementation in cases where we know it is safe to do so,
// and delegate to our base class (which will call into Read) when we are not sure.
//TODO:
//if (this.GetType() != typeof(StreamReader))
//return base.ReadLineAsync();
//if (mStream == null)
//__Error.ReaderClosed();
//CheckAsyncTaskInProgress();
Task < String > task = ReadLineAsyncInternal ( ) ;
//_asyncReadTask = task;
return task ;
}
class ReadLineTask : Task < String >
{
StreamReader mStreamReader ;
WaitEvent mDoneEvent = new WaitEvent ( ) ~ delete _ ;
2020-03-09 06:34:16 -07:00
public this ( StreamReader streamReader )
2019-08-23 11:56:54 -07:00
{
mStreamReader = streamReader ;
ThreadPool . QueueUserWorkItem ( new = > Proc ) ;
}
2020-03-09 06:34:16 -07:00
public ~ this ( )
2019-08-23 11:56:54 -07:00
{
//Debug.WriteLine("ReadLineTask ~this waiting {0}", this);
mDoneEvent . WaitFor ( ) ;
//Debug.WriteLine("ReadLineTask ~this done {0}", this);
delete m_result ;
}
void Proc ( )
{
//Debug.WriteLine("ReadLineTask Proc start {0}", this);
m_result = new String ( ) ;
var result = mStreamReader . ReadLine ( m_result ) ;
//Debug.WriteLine("ReadLineTask Proc finishing {0}", this);
Finish ( false ) ;
Ref ( ) ;
if ( result case . Ok )
Notify ( false ) ;
mDoneEvent . Set ( ) ;
Deref ( ) ;
}
}
private Task < String > ReadLineAsyncInternal ( )
{
/ * if ( CharPos_Prop = = CharLen_Prop & & ( await ReadBufferAsync ( ) . ConfigureAwait ( false ) ) = = 0 )
return null ;
String sb = null ;
do
{
char8 [ ] tmpCharBuffer = CharBuffer_Prop ;
int tmpCharLen = CharLen_Prop ;
int tmpCharPos = CharPos_Prop ;
int i = tmpCharPos ;
do
{
char8 ch = tmpCharBuffer [ i ] ;
2021-10-30 00:11:37 +03:00
// Note the following common line feed chars:
2019-08-23 11:56:54 -07:00
// \n - UNIX \r\n - DOS \r - Mac
if ( ch = = '\r' | | ch = = '\n' )
{
String s ;
if ( sb ! = null )
{
sb . Append ( tmpCharBuffer , tmpCharPos , i - tmpCharPos ) ;
s = sb ; //.ToString();
}
else
{
s = new String ( tmpCharBuffer , tmpCharPos , i - tmpCharPos ) ;
}
CharPos_Prop = tmpCharPos = i + 1 ;
if ( ch = = '\r' & & ( tmpCharPos < tmpCharLen | | ( await ReadBufferAsync ( ) . ConfigureAwait ( false ) ) > 0 ) )
{
tmpCharPos = CharPos_Prop ;
if ( CharBuffer_Prop [ tmpCharPos ] = = '\n' )
CharPos_Prop = + + tmpCharPos ;
}
return s ;
}
i + + ;
} while ( i < tmpCharLen ) ;
i = tmpCharLen - tmpCharPos ;
if ( sb = = null ) sb = new String ( i + 80 ) ;
sb . Append ( tmpCharBuffer , tmpCharPos , i ) ;
} while ( await ReadBufferAsync ( ) . ConfigureAwait ( false ) > 0 ) ;
return sb . ToString ( ) ; * /
ReadLineTask task = new ReadLineTask ( this ) ;
return task ;
}
public Result < void > ReadToEnd ( String outText )
{
outText . Reserve ( ( . ) mStream . Length + 1 ) ;
while ( true )
{
Try ! ( ReadBuffer ( ) ) ;
if ( mCharLen = = 0 )
break ;
outText . Append ( StringView ( mCharBuffer , 0 , mCharLen ) ) ;
}
return . Ok ;
}
// Trims the preamble bytes from the byteBuffer. This routine can be called multiple times
// and we will buffer the bytes read until the preamble is matched or we determine that
// there is no match. If there is no match, every byte read previously will be available
// for further consumption. If there is a match, we will compress the buffer for the
// leading preamble bytes
private bool IsPreamble ( )
{
if ( ! mCheckPreamble )
return mCheckPreamble ;
//Contract.Assert(bytePos <= _preamble.Length, "_compressPreamble was called with the current bytePos greater than the preamble buffer length. Are two threads using this StreamReader at the same time?");
int len = ( mByteLen > = ( mPreamble . Count ) ) ? ( mPreamble . Count - mBytePos ) : ( mByteLen - mBytePos ) ;
for ( int32 i = 0 ; i < len ; i + + , mBytePos + + )
{
if ( mByteBuffer [ mBytePos ] ! = mPreamble [ mBytePos ] )
{
mBytePos = 0 ;
mCheckPreamble = false ;
break ;
}
}
Contract . Assert ( mBytePos < = mPreamble . Count , "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?" ) ;
if ( mCheckPreamble )
{
if ( mBytePos = = mPreamble . Count )
{
// We have a match
CompressBuffer ( mPreamble . Count ) ;
mBytePos = 0 ;
mCheckPreamble = false ;
mDetectEncoding = false ;
}
}
return mCheckPreamble ;
}
// Trims n bytes from the front of the buffer.
private void CompressBuffer ( int n )
{
//Contract.Assert(byteLen >= n, "CompressBuffer was called with a number of bytes greater than the current buffer length. Are two threads using this StreamReader at the same time?");
//Buffer.InternalBlockCopy(byteBuffer, n, byteBuffer, 0, byteLen - n);
mByteBuffer . CopyTo ( mByteBuffer , n , 0 , mByteLen - n ) ;
mByteLen - = ( int32 ) n ;
}
void DetectEncoding ( )
{
mEncoding = Encoding . DetectEncoding ( mByteBuffer , var bomSize ) ;
if ( mEncoding ! = null )
{
if ( bomSize > 0 )
CompressBuffer ( bomSize ) ;
mDetectEncoding = false ;
}
else if ( mByteLen > 4 )
{
mDetectEncoding = false ;
}
}
2023-04-17 15:16:19 -07:00
protected virtual Result < int > ReadBuffer ( bool zeroWait = false )
2019-08-23 11:56:54 -07:00
{
mCharLen = 0 ;
mCharPos = 0 ;
if ( ! mCheckPreamble )
mByteLen = 0 ;
repeat
{
if ( mCheckPreamble )
{
//Contract.Assert(bytePos <= _preamble.Length, "possible bug in _compressPreamble. Are two threads using this StreamReader at the same time?");
2023-04-17 15:16:19 -07:00
int len = Try ! ( mStream . TryRead ( . ( mByteBuffer , mBytePos , mByteBuffer . Count - mBytePos ) , zeroWait ? 0 : - 1 ) ) ;
2019-08-23 11:56:54 -07:00
/ * switch ( mStream . Read ( mByteBuffer , mBytePos , mByteBuffer . Length - mBytePos ) )
{
case . Ok ( var gotLen ) :
len = gotLen ;
break ;
case . Err : return . Err ;
} * /
//Contract.Assert(len >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
if ( len = = 0 )
{
// EOF but we might have buffered bytes from previous
// attempt to detect preamble that needs to be decoded now
if ( mByteLen > 0 )
{
//TODO:
/ * char8Len + = decoder . GetChars ( byteBuffer , 0 , byteLen , char8Buffer , char8Len ) ;
// Need to zero out the byteLen after we consume these bytes so that we don't keep infinitely hitting this code path
bytePos = byteLen = 0 ; * /
}
return mCharLen ;
}
mByteLen + = len ;
}
else
{
//Contract.Assert(bytePos == 0, "bytePos can be non zero only when we are trying to _checkPreamble. Are two threads using this StreamReader at the same time?");
2023-04-17 15:16:19 -07:00
mByteLen = Try ! ( mStream . TryRead ( . ( mByteBuffer , 0 , mByteBuffer . Count ) , zeroWait ? 0 : - 1 ) ) ;
2019-08-23 11:56:54 -07:00
/ * switch ( mStream . Read ( mByteBuffer , 0 , mByteBuffer . Length ) )
{
case . Ok ( var byteLen ) :
mByteLen = byteLen ;
break ;
case . Err : return . Err ;
} * /
//Contract.Assert(byteLen >= 0, "Stream.Read returned a negative number! This is a bug in your stream class.");
if ( mByteLen = = 0 ) // We're at EOF
return mCharLen ;
}
// _isBlocked == whether we read fewer bytes than we asked for.
// Note we must check it here because CompressBuffer or
// DetectEncoding will change byteLen.
mIsBlocked = ( mByteLen < mByteBuffer . Count ) ;
// Check for preamble before detect encoding. This is not to override the
// user suppplied Encoding for the one we implicitly detect. The user could
// customize the encoding which we will loose, such as ThrowOnError on UTF8
if ( IsPreamble ( ) )
continue ;
// If we're supposed to detect the encoding and haven't done so yet,
// do it. Note this may need to be called more than once.
if ( mDetectEncoding & & mByteLen > = 2 )
DetectEncoding ( ) ;
else if ( mEncoding = = null )
mEncoding = Encoding . ASCII ;
switch ( mEncoding . DecodeToUTF8 ( . ( mByteBuffer , 0 , mByteLen ) , . ( & mCharBuffer [ mCharLen ] , mCharBuffer . Count - mCharLen ) ) )
{
case . Ok ( let outChars ) :
mCharLen + = outChars ;
Debug . Assert ( outChars < = mCharBuffer . Count ) ;
break ;
case . Err ( let err ) :
switch ( err )
{
case . PartialDecode ( let decodedBytes , let outChars ) :
//TODO: Handle this partial read...
Debug . Assert ( outChars < = mCharBuffer . Count ) ;
mCharLen + = outChars ;
break ;
default :
}
}
2023-04-17 15:16:19 -07:00
if ( ( mPendingNewlineCheck ) & & ( mCharPos < mCharLen ) )
{
if ( mCharBuffer [ mCharPos ] = = '\n' ) mCharPos + + ;
mPendingNewlineCheck = false ;
}
2019-08-23 11:56:54 -07:00
}
2023-04-17 15:16:19 -07:00
while ( mCharLen = = mCharPos ) ;
2019-08-23 11:56:54 -07:00
2021-10-30 00:11:37 +03:00
//Console.WriteLine("ReadBuffer called. chars: "+char8Len);
2019-08-23 11:56:54 -07:00
return mCharLen ;
}
int GetChars ( uint8 [ ] byteBuffer , int byteOffset , int byteLength , char8 [ ] char8Buffer , int char8Offset )
{
//TODO: This only handles UTF-8 to UTF-8
for ( int32 i = 0 ; i < byteLength ; i + + )
char8Buffer [ char8Offset + i ] = ( char8 ) byteBuffer [ i + byteOffset ] ;
return byteLength ;
}
2019-09-07 06:40:26 -07:00
// Reads a line. A line is defined as a sequence of characters followed by
2019-08-23 11:56:54 -07:00
// 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 input stream has been reached.
//
public Result < void > ReadLine ( String strBuffer )
{
if ( mStream = = null )
{
return . Err ;
//__Error.ReaderClosed();
}
#if FEATURE_ASYNC_IO
CheckAsyncTaskInProgress ( ) ;
#endif
if ( mCharPos = = mCharLen )
{
if ( Try ! ( ReadBuffer ( ) ) = = 0 ) return . Err ;
}
repeat
{
int i = mCharPos ;
repeat
{
char8 ch = mCharBuffer [ i ] ;
2021-10-30 00:11:37 +03:00
// Note the following common line feed chars:
2019-08-23 11:56:54 -07:00
// \n - UNIX \r\n - DOS \r - Mac
if ( ch = = '\r' | | ch = = '\n' )
{
/ * String s ;
if ( sb ! = null )
{
sb . Append ( char8Buffer , char8Pos , i - char8Pos ) ;
s = sb . ToString ( ) ;
}
else
{
s = new String ( char8Buffer , char8Pos , i - char8Pos ) ;
} * /
strBuffer . Append ( mCharBuffer . CArray ( ) + mCharPos , i - mCharPos ) ;
mCharPos = i + 1 ;
2023-04-17 15:16:19 -07:00
if ( ch = = '\r' )
2019-08-23 11:56:54 -07:00
{
2023-04-17 15:16:19 -07:00
if ( mCharPos < mCharLen )
{
if ( mCharBuffer [ mCharPos ] = = '\n' ) mCharPos + + ;
}
else
{
mPendingNewlineCheck = true ;
}
2019-08-23 11:56:54 -07:00
}
2023-04-17 15:16:19 -07:00
2019-08-23 11:56:54 -07:00
return . Ok ;
}
i + + ;
}
while ( i < mCharLen ) ;
i = mCharLen - mCharPos ;
//if (sb == null) sb = new StringBuilder(i + 80);
//sb.Append(char8Buffer, char8Pos, i);
strBuffer . Append ( mCharBuffer . CArray ( ) + mCharPos , i ) ;
}
while ( Try ! ( ReadBuffer ( ) ) > 0 ) ;
return . Ok ;
}
2019-09-27 13:03:47 -07:00
public Result < char8 > Read ( )
{
if ( mStream = = null )
return . Err ;
if ( mCharPos = = mCharLen )
{
if ( Try ! ( ReadBuffer ( ) ) = = 0 ) return . Err ;
}
return mCharBuffer [ mCharPos + + ] ;
}
2019-08-23 11:56:54 -07:00
public struct LineReader : IEnumerator < Result < StringView > >
{
StreamReader mStreamReader ;
String mCurrentLine ;
Result < void > mLastReadResult ;
2020-03-09 06:34:16 -07:00
public this ( StreamReader streamReader )
2019-08-23 11:56:54 -07:00
{
mStreamReader = streamReader ;
mCurrentLine = new String ( ) ;
mLastReadResult = default ( Result < void > ) ;
}
public Result < StringView > Current
{
get
{
Try ! ( mLastReadResult ) ;
return ( StringView ) mCurrentLine ;
}
}
public void Reset ( )
{
}
public bool MoveNext ( ) mut
{
mCurrentLine . Clear ( ) ;
mLastReadResult = mStreamReader . ReadLine ( mCurrentLine ) ;
return ! ( mLastReadResult case . Err ) ;
}
public void Dispose ( ) mut
{
delete mCurrentLine ;
mCurrentLine = null ;
}
public Result < Result < StringView > > GetNext ( ) mut
{
if ( ! MoveNext ( ) )
return . Err ;
return Current ;
}
}
}
}