Browse Source

add: MemoryStreams

Akeit0 6 months ago
parent
commit
20305f40fb
1 changed files with 219 additions and 0 deletions
  1. 219 0
      src/Lua/IO/MemoryStreams.cs

+ 219 - 0
src/Lua/IO/MemoryStreams.cs

@@ -0,0 +1,219 @@
+namespace Lua.IO;
+
+public sealed class ByteMemoryStream(ReadOnlyMemory<byte> bytes) : ILuaIOStream
+{
+    private int position;
+    private bool disposed;
+
+    public ByteMemoryStream(byte[] bytes) : this(bytes.AsMemory())
+    {
+        if (bytes is null)
+            throw new ArgumentNullException(nameof(bytes));
+    }
+
+    public LuaFileOpenMode Mode => LuaFileOpenMode.Read;
+
+    public LuaFileContentType ContentType => LuaFileContentType.Binary;
+
+    public void Dispose()
+    {
+        disposed = true;
+    }
+
+    public ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken)
+    {
+        ThrowIfDisposed();
+        cancellationToken.ThrowIfCancellationRequested();
+
+        var remaining = bytes.Slice(position);
+        position = bytes.Length;
+        return new(new LuaFileContent(remaining));
+    }
+
+    public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
+    {
+        ThrowIfDisposed();
+        cancellationToken.ThrowIfCancellationRequested();
+
+        throw new InvalidOperationException("Cannot read lines string from a binary stream.");
+    }
+
+    public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken)
+    {
+        ThrowIfDisposed();
+        cancellationToken.ThrowIfCancellationRequested();
+        throw new InvalidOperationException("Cannot read lines string from a binary stream.");
+    }
+
+    public ValueTask WriteAsync(LuaFileContent content, CancellationToken cancellationToken)
+    {
+        throw new NotSupportedException("Stream is read-only");
+    }
+
+    public ValueTask FlushAsync(CancellationToken cancellationToken)
+    {
+        ThrowIfDisposed();
+        return default;
+    }
+
+    public void SetVBuf(LuaFileBufferingMode mode, int size)
+    {
+        // No-op for memory streams
+    }
+
+    public long Seek(long offset, SeekOrigin origin)
+    {
+        ThrowIfDisposed();
+
+        long newPosition = origin switch
+        {
+            SeekOrigin.Begin => offset,
+            SeekOrigin.Current => position + offset,
+            SeekOrigin.End => bytes.Length + offset,
+            _ => throw new ArgumentException("Invalid seek origin", nameof(origin))
+        };
+
+        if (newPosition < 0 || newPosition > bytes.Length)
+            throw new ArgumentOutOfRangeException(nameof(offset), "Seek position is out of range");
+
+        position = (int)newPosition;
+        return position;
+    }
+
+    private void ThrowIfDisposed()
+    {
+        if (disposed)
+            throw new ObjectDisposedException(nameof(ByteMemoryStream));
+    }
+}
+
+public class CharMemoryStream(ReadOnlyMemory<char> contents) : ILuaIOStream
+{
+    protected int Position;
+    private bool disposed;
+
+    public LuaFileOpenMode Mode => LuaFileOpenMode.Read;
+
+    public LuaFileContentType ContentType => LuaFileContentType.Text;
+
+    public void Dispose()
+    {
+        disposed = true;
+    }
+
+    public virtual ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken)
+    {
+        ThrowIfDisposed();
+
+        cancellationToken.ThrowIfCancellationRequested();
+        if (Position >= contents.Length)
+            return new(new LuaFileContent(""));
+
+        var remaining = contents[Position..];
+        Position = contents.Length;
+        return new(new LuaFileContent(remaining));
+    }
+
+    public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
+    {
+        ThrowIfDisposed();
+        cancellationToken.ThrowIfCancellationRequested();
+
+        if (Position >= contents.Length)
+            return new((string?)null);
+
+        var remainingSpan = contents.Slice(Position).Span;
+        var newlineIndex = remainingSpan.IndexOf('\n');
+
+        string result;
+        if (newlineIndex == -1)
+        {
+            // Read to end
+            result = remainingSpan.ToString();
+            Position = contents.Length;
+        }
+        else
+        {
+            // Read up to newline
+            var lineSpan = remainingSpan[..newlineIndex];
+            // Remove CR if present
+            if (lineSpan.Length > 0 && lineSpan[^1] == '\r')
+                lineSpan = lineSpan[..^1];
+
+            result = lineSpan.ToString();
+            Position += newlineIndex + 1;
+        }
+
+        return new(result);
+    }
+
+    public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken)
+    {
+        ThrowIfDisposed();
+        cancellationToken.ThrowIfCancellationRequested();
+
+        if (Position >= contents.Length)
+            return new((string?)null);
+
+        var available = contents.Length - Position;
+        var toRead = Math.Min(count, available);
+
+        var result = contents.Slice(Position, toRead).ToString();
+        Position += toRead;
+
+        return new(result);
+    }
+
+    public ValueTask WriteAsync(LuaFileContent content, CancellationToken cancellationToken)
+    {
+        throw new NotSupportedException("Stream is read-only");
+    }
+
+    public ValueTask FlushAsync(CancellationToken cancellationToken)
+    {
+        ThrowIfDisposed();
+        return default;
+    }
+
+    public void SetVBuf(LuaFileBufferingMode mode, int size)
+    {
+        // No-op for memory streams
+    }
+
+    public long Seek(long offset, SeekOrigin origin)
+    {
+        ThrowIfDisposed();
+
+        long newPosition = origin switch
+        {
+            SeekOrigin.Begin => offset,
+            SeekOrigin.Current => Position + offset,
+            SeekOrigin.End => contents.Length + offset,
+            _ => throw new ArgumentException("Invalid seek origin", nameof(origin))
+        };
+
+        if (newPosition < 0 || newPosition > contents.Length)
+            throw new ArgumentOutOfRangeException(nameof(offset), "Seek position is out of range");
+
+        Position = (int)newPosition;
+        return Position;
+    }
+
+    protected void ThrowIfDisposed()
+    {
+        if (disposed)
+            throw new ObjectDisposedException(nameof(StringStream));
+    }
+}
+
+public sealed class StringStream(string content) : CharMemoryStream(content.AsMemory())
+{
+    public override ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken)
+    {
+        ThrowIfDisposed();
+        cancellationToken.ThrowIfCancellationRequested();
+        if (Position == 0)
+            return new(new LuaFileContent(content));
+        return base.ReadAllAsync(cancellationToken);
+    }
+}