Browse Source

change: abstract async file and stream system

Akeit0 7 months ago
parent
commit
39be24e2c7

+ 167 - 0
src/Lua/ILuaFileManager.cs

@@ -0,0 +1,167 @@
+namespace Lua;
+
+public interface ILuaFileManager
+{
+    public bool IsReadable(string path);
+    public ValueTask<LuaFile> LoadModuleAsync(string path, CancellationToken cancellationToken);
+    public IStream Open(string path, FileMode mode, FileAccess access);
+    public void Rename (string oldName, string newName);
+    public void Remove (string path);
+}
+
+
+public interface IStream : IDisposable
+{
+    public IStreamReader? Reader { get; }
+    public IStreamWriter? Writer { get; }
+
+    public long Seek(long offset, SeekOrigin origin);
+
+    public void SetLength(long value);
+
+    public bool CanRead { get; }
+    public bool CanSeek { get; }
+    public bool CanWrite { get; }
+    public long Length { get; }
+    public long Position { get; set; }
+}
+
+public interface IStreamReader : IDisposable
+{
+    public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken);
+    public ValueTask<string> ReadToEndAsync(CancellationToken cancellationToken);
+    public ValueTask<int> ReadByteAsync(CancellationToken cancellationToken);
+}
+
+public interface IStreamWriter : IDisposable
+{
+    public ValueTask WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken);
+    public ValueTask FlushAsync(CancellationToken cancellationToken);
+
+    public void SetVBuf(string mode, int size);
+}
+
+public sealed class SystemFileManager : ILuaFileManager
+{
+    public static readonly SystemFileManager Instance = new();
+
+    public bool IsReadable(string path)
+    {
+        if (!File.Exists(path)) return false;
+        try
+        {
+            File.Open(path, FileMode.Open, FileAccess.Read).Dispose();
+            return true;
+        }
+        catch (Exception)
+        {
+            return false;
+        }
+    }
+
+    public ValueTask<LuaFile> LoadModuleAsync(string path, CancellationToken cancellationToken)
+    {
+        var bytes = File.ReadAllBytes(path);
+        return new(new LuaFile(bytes));
+    }
+
+    public IStream Open(string path, FileMode mode, FileAccess access)
+    {
+        return new SystemStream(File.Open(path, mode, access));
+    }
+
+    public ValueTask WriteAllTextAsync(string path, string text, CancellationToken cancellationToken)
+    {
+        File.WriteAllText(path, text);
+        return new();
+    }
+    public void Rename(string oldName, string newName)
+    {
+        if (oldName == newName) return;
+        File.Move(oldName, newName);
+        File.Delete(oldName);
+        
+    }
+    public void Remove(string path)
+    {
+        File.Delete(path);
+    }
+}
+
+public sealed class SystemStreamReader(StreamReader streamReader) : IStreamReader
+{
+    public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
+    {
+        return new(streamReader.ReadLine());
+    }
+
+    public ValueTask<string> ReadToEndAsync(CancellationToken cancellationToken)
+    {
+        return new(streamReader.ReadToEnd());
+    }
+
+    public ValueTask<int> ReadByteAsync(CancellationToken cancellationToken)
+    {
+        return new(streamReader.Read());
+    }
+
+    public void Dispose()
+    {
+        streamReader.Dispose();
+    }
+}
+
+public sealed class SystemStreamWriter(StreamWriter streamWriter) : IStreamWriter
+{
+    public ValueTask WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken)
+    {
+        streamWriter.Write(buffer.Span);
+        return new();
+    }
+
+    public ValueTask WriteLineAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken)
+    {
+        streamWriter.WriteLine(buffer.Span);
+        return new();
+    }
+
+    public ValueTask FlushAsync(CancellationToken cancellationToken)
+    {
+        streamWriter.Flush();
+        return new();
+    }
+
+    public void SetVBuf(string mode, int size)
+    {
+        // Ignore size parameter
+        streamWriter.AutoFlush = mode is "no" or "line";
+    }
+
+    public void Dispose()
+    {
+        streamWriter.Dispose();
+    }
+}
+
+public sealed class SystemStream(Stream fileStream) : IStream
+{
+    public IStreamReader? Reader => fileStream.CanRead ? new SystemStreamReader(new(fileStream)) : null;
+    public IStreamWriter? Writer => fileStream.CanWrite ? new SystemStreamWriter(new(fileStream)) : null;
+
+    public long Seek(long offset, SeekOrigin origin) => fileStream.Seek(offset, origin);
+
+    public void SetLength(long value) => fileStream.SetLength(value);
+
+    public bool CanRead => fileStream.CanRead;
+    public bool CanSeek => fileStream.CanSeek;
+    public bool CanWrite => fileStream.CanWrite;
+    public long Length => fileStream.Length;
+
+    public long Position
+    {
+        get => fileStream.Position;
+        set => fileStream.Position = value;
+    }
+
+    public void Dispose() => fileStream.Dispose();
+}

+ 71 - 0
src/Lua/LuaFile.cs

@@ -0,0 +1,71 @@
+using System.Buffers;
+
+namespace Lua;
+
+public enum LuaFileType
+{
+    Text,
+    Bytes
+}
+
+public readonly struct LuaFile : IDisposable
+{
+    public LuaFileType Type => type;
+
+    readonly LuaFileType type;
+    readonly object referenceValue;
+
+    public LuaFile(string text)
+    {
+        type = LuaFileType.Text;
+        referenceValue = text;
+    }
+
+    public LuaFile(byte[] bytes)
+    {
+        type = LuaFileType.Bytes;
+        referenceValue = bytes;
+    }
+
+    public LuaFile(IMemoryOwner<char> bytes)
+    {
+        type = LuaFileType.Text;
+        referenceValue = bytes;
+    }
+
+    public LuaFile(IMemoryOwner<byte> bytes)
+    {
+        type = LuaFileType.Bytes;
+        referenceValue = bytes;
+    }
+
+    public ReadOnlySpan<char> ReadText()
+    {
+        if (type != LuaFileType.Text) throw new Exception(); // TODO: add message
+        if (referenceValue is IMemoryOwner<char> mem)
+        {
+            return mem.Memory.Span;
+        }
+
+        return ((string)referenceValue);
+    }
+
+    public ReadOnlySpan<byte> ReadBytes()
+    {
+        if (type != LuaFileType.Bytes) throw new Exception(); // TODO: add message
+        if (referenceValue is IMemoryOwner<byte> mem)
+        {
+            return mem.Memory.Span;
+        }
+
+        return (byte[])referenceValue;
+    }
+
+    public void Dispose()
+    {
+        if (referenceValue is IDisposable memoryOwner)
+        {
+            memoryOwner.Dispose();
+        }
+    }
+}

+ 2 - 0
src/Lua/LuaState.cs

@@ -37,6 +37,8 @@ public sealed class LuaState
     public LuaThreadAccess TopLevelAccess => new (mainThread, 0);
 
     public ILuaModuleLoader ModuleLoader { get; set; } = FileModuleLoader.Instance;
+    
+    public ILuaFileManager FileManager { get; set; } = SystemFileManager.Instance;
 
     // metatables
     LuaTable? nilMetatable;

+ 13 - 0
src/Lua/LuaStateExtensions.cs

@@ -23,4 +23,17 @@ public static class LuaStateExtensions
     {
         return state.TopLevelAccess.DoFileAsync(path, cancellationToken);
     }
+
+    public static async ValueTask<LuaClosure> LoadFileAsync(this LuaState state, string fileName, string mode, LuaTable? environment, CancellationToken cancellationToken)
+    {
+        var name = "@" + fileName;
+        LuaClosure closure;
+        {
+            using var file = await state.FileManager.LoadModuleAsync(fileName, cancellationToken);
+            closure = file.Type == LuaFileType.Bytes
+                ? state.Load(file.ReadBytes(), name, mode, environment)
+                : state.Load(file.ReadText(), name, environment);
+        }
+        return closure;
+    }
 }

+ 2 - 6
src/Lua/Runtime/LuaThreadAccessExtensions.cs

@@ -28,9 +28,7 @@ public static class LuaThreadAccessAccessExtensions
     public static async ValueTask<int> DoFileAsync(this LuaThreadAccess access, string path, Memory<LuaValue> buffer, CancellationToken cancellationToken = default)
     {
         access.ThrowIfInvalid();
-        var bytes = File.ReadAllBytes(path);
-        var fileName = "@" + path;
-        var closure = access.State.Load(bytes, fileName);
+        var closure = await access.State.LoadFileAsync(path, "bt", null, cancellationToken);
         var count = await access.RunAsync(closure, 0, cancellationToken);
         using var results = access.ReadReturnValues(count);
         results.AsSpan()[..Math.Min(buffer.Length, results.Length)].CopyTo(buffer.Span);
@@ -39,9 +37,7 @@ public static class LuaThreadAccessAccessExtensions
 
     public static async ValueTask<LuaValue[]> DoFileAsync(this LuaThreadAccess access, string path, CancellationToken cancellationToken = default)
     {
-        var bytes = File.ReadAllBytes(path);
-        var fileName = "@" + path;
-        var closure = access.State.Load(bytes, fileName);
+        var closure = await access.State.LoadFileAsync(path, "bt", null, cancellationToken);
         var count = await access.RunAsync(closure, 0, cancellationToken);
         using var results = access.ReadReturnValues(count);
         return results.AsSpan().ToArray();

+ 4 - 9
src/Lua/Standard/BasicLibrary.cs

@@ -89,10 +89,7 @@ public sealed class BasicLibrary
     {
         var arg0 = context.GetArgument<string>(0);
         context.Thread.Stack.PopUntil(context.ReturnFrameBase);
-
-        var bytes = File.ReadAllBytes(arg0);
-        var fileName = "@" + arg0;
-        var closure = context.State.Load(bytes, fileName);
+        var closure = await context.State.LoadFileAsync(arg0, "bt",null, cancellationToken);
         return await context.Access.RunAsync(closure, cancellationToken);
     }
 
@@ -148,7 +145,7 @@ public sealed class BasicLibrary
         return context.Return(IPairsIterator, arg0, 0);
     }
 
-    public ValueTask<int> LoadFile(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
+    public async ValueTask<int> LoadFile(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
         var arg0 = context.GetArgument<string>(0);
         var mode = context.HasArgument(1)
@@ -161,13 +158,11 @@ public sealed class BasicLibrary
         // do not use LuaState.DoFileAsync as it uses the newExecutionContext
         try
         {
-            var bytes = File.ReadAllBytes(arg0);
-            var fileName = "@" + arg0;
-            return new(context.Return(context.State.Load(bytes, fileName, mode, arg2)));
+            return context.Return(await context.State.LoadFileAsync(arg0,  mode, arg2,cancellationToken));
         }
         catch (Exception ex)
         {
-            return new(context.Return(LuaValue.Nil, ex.Message));
+            return context.Return(LuaValue.Nil, ex.Message);
         }
     }
 

+ 43 - 38
src/Lua/Standard/FileHandle.cs

@@ -32,9 +32,9 @@ public class FileHandle : ILuaUserData
         }
     });
 
-    Stream stream;
-    StreamWriter? writer;
-    StreamReader? reader;
+    IStream stream;
+    IStreamWriter? writer;
+    IStreamReader? reader;
     bool isClosed;
 
     public bool IsClosed => Volatile.Read(ref isClosed);
@@ -49,31 +49,33 @@ public class FileHandle : ILuaUserData
         fileHandleMetatable[Metamethods.Index] = IndexMetamethod;
     }
 
-    public FileHandle(Stream stream)
+    public FileHandle(Stream stream) : this(new SystemStream(stream)) { }
+
+    public FileHandle(IStream stream)
     {
         this.stream = stream;
-        if (stream.CanRead) reader = new StreamReader(stream);
-        if (stream.CanWrite) writer = new StreamWriter(stream);
+        if (stream.CanRead) reader = stream.Reader;
+        if (stream.CanWrite) writer = stream.Writer;
     }
 
-    public string? ReadLine()
+    public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
     {
-        return reader!.ReadLine();
+        return reader!.ReadLineAsync(cancellationToken);
     }
 
-    public string ReadToEnd()
+    public ValueTask<string> ReadToEndAsync(CancellationToken cancellationToken)
     {
-        return reader!.ReadToEnd();
+        return reader!.ReadToEndAsync(cancellationToken);
     }
 
-    public int ReadByte()
+    public ValueTask<int> ReadByteAsync(CancellationToken cancellationToken)
     {
-        return stream.ReadByte();
+        return reader!.ReadByteAsync(cancellationToken);
     }
 
-    public void Write(ReadOnlySpan<char> buffer)
+    public ValueTask WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken)
     {
-        writer!.Write(buffer);
+        return writer!.WriteAsync(buffer, cancellationToken);
     }
 
     public long Seek(string whence, long offset)
@@ -99,19 +101,14 @@ public class FileHandle : ILuaUserData
         return stream.Position;
     }
 
-    public void Flush()
+    public ValueTask FlushAsync(CancellationToken cancellationToken)
     {
-        writer!.Flush();
+        return writer!.FlushAsync(cancellationToken);
     }
 
     public void SetVBuf(string mode, int size)
     {
-        // Ignore size parameter
-
-        if (writer != null)
-        {
-            writer.AutoFlush = mode is "no" or "line";
-        }
+        writer!.SetVBuf(mode, size);
     }
 
     public void Close()
@@ -123,10 +120,18 @@ public class FileHandle : ILuaUserData
         {
             reader.Dispose();
         }
+        else if (writer != null)
+        {
+            writer.Dispose();
+        }
         else
         {
-            stream.Close();
+            stream.Dispose();
         }
+
+        stream = null!;
+        writer = null;
+        reader = null;
     }
 
     static readonly LuaFunction CloseFunction = new("close", (context, cancellationToken) =>
@@ -144,18 +149,18 @@ public class FileHandle : ILuaUserData
         }
     });
 
-    static readonly LuaFunction FlushFunction = new("flush", (context, cancellationToken) =>
+    static readonly LuaFunction FlushFunction = new("flush", async (context, cancellationToken) =>
     {
         var file = context.GetArgument<FileHandle>(0);
 
         try
         {
-            file.Flush();
-            return new(context.Return(true));
+            await file.FlushAsync(cancellationToken);
+            return context.Return(true);
         }
         catch (IOException ex)
         {
-            return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult));
+            return (context.Return(LuaValue.Nil, ex.Message, ex.HResult));
         }
     });
 
@@ -167,22 +172,22 @@ public class FileHandle : ILuaUserData
             : "*l";
 
 
-        return new(context.Return(new CSharpClosure("iterator", [new(file), format], static (context, cancellationToken) =>
+        return new(context.Return(new CSharpClosure("iterator", [new(file), format], static async (context, cancellationToken) =>
         {
-            var upValues = context.GetCsClosure()!.UpValues.AsSpan();
-            var file = upValues[0].Read<FileHandle>();
+            var upValues = context.GetCsClosure()!.UpValues.AsMemory();
+            var file = upValues.Span[0].Read<FileHandle>();
             context.Return();
-            var resultCount = IOHelper.Read(context.Thread, file, "lines", 0, upValues[1..], true);
-            return new(resultCount);
+            var resultCount = await IOHelper.ReadAsync(context.Thread, file, "lines", 0, upValues[1..], true, cancellationToken);
+            return resultCount;
         })));
     });
 
-    static readonly LuaFunction ReadFunction = new("read", (context, cancellationToken) =>
+    static readonly LuaFunction ReadFunction = new("read", async (context, cancellationToken) =>
     {
         var file = context.GetArgument<FileHandle>(0);
         context.Return();
-        var resultCount = IOHelper.Read(context.Thread, file, "read", 1, context.Arguments[1..], false);
-        return new(resultCount);
+        var resultCount = await IOHelper.ReadAsync(context.Thread, file, "read", 1, context.Arguments[1..].ToArray(), false, cancellationToken);
+        return resultCount;
     });
 
     static readonly LuaFunction SeekFunction = new("seek", (context, cancellationToken) =>
@@ -223,10 +228,10 @@ public class FileHandle : ILuaUserData
         return new(context.Return(true));
     });
 
-    static readonly LuaFunction WriteFunction = new("write", (context, cancellationToken) =>
+    static readonly LuaFunction WriteFunction = new("write", async (context, cancellationToken) =>
     {
         var file = context.GetArgument<FileHandle>(0);
-        var resultCount = IOHelper.Write(file, "write", context with{ArgumentCount = context.ArgumentCount-1});
-        return new(resultCount);
+        var resultCount = await IOHelper.WriteAsync(file, "write", context with { ArgumentCount = context.ArgumentCount - 1 }, cancellationToken);
+        return resultCount;
     });
 }

+ 20 - 21
src/Lua/Standard/IOLibrary.cs

@@ -42,18 +42,18 @@ public sealed class IOLibrary
         }
     }
 
-    public ValueTask<int> Flush(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
+    public async ValueTask<int> Flush(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
         var file = context.State.Registry["stdout"].Read<FileHandle>();
 
         try
         {
-            file.Flush();
-            return new(context.Return(true));
+            await file.FlushAsync(cancellationToken);
+            return context.Return(true);
         }
         catch (IOException ex)
         {
-            return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult));
+            return context.Return(LuaValue.Nil, ex.Message, ex.HResult);
         }
     }
 
@@ -63,7 +63,7 @@ public sealed class IOLibrary
 
         if (context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil)
         {
-            return new(context.Return( registry["stdin"]));
+            return new(context.Return(registry["stdin"]));
         }
 
         var arg = context.Arguments[0];
@@ -74,7 +74,7 @@ public sealed class IOLibrary
         }
         else
         {
-            var stream = File.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite);
+            var stream = context.State.FileManager.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite);
             var handle = new FileHandle(stream);
             registry["stdin"] = new(handle);
             return new(context.Return(new LuaValue(handle)));
@@ -86,17 +86,17 @@ public sealed class IOLibrary
         if (context.ArgumentCount == 0)
         {
             var file = context.State.Registry["stdin"].Read<FileHandle>();
-            return new(context.Return(new CSharpClosure("iterator", [new(file)], static (context, ct) =>
+            return new(context.Return(new CSharpClosure("iterator", [new(file)], static async (context, cancellationToken) =>
             {
                 var file = context.GetCsClosure()!.UpValues[0].Read<FileHandle>();
                 context.Return();
-                var resultCount = IOHelper.Read(context.Thread, file, "lines", 0, [], true);
+                var resultCount = await IOHelper.ReadAsync(context.Thread, file, "lines", 0, Memory<LuaValue>.Empty, true, cancellationToken);
                 if (resultCount > 0 && context.Thread.Stack.Get(context.ReturnFrameBase).Type is LuaValueType.Nil)
                 {
                     file.Close();
                 }
 
-                return new(resultCount);
+                return resultCount;
             })));
         }
         else
@@ -112,20 +112,20 @@ public sealed class IOLibrary
             upValues[0] = new(file);
             context.Arguments[1..].CopyTo(upValues[1..]);
 
-            return new(context.Return(new CSharpClosure("iterator", upValues, static (context, ct) =>
+            return new(context.Return(new CSharpClosure("iterator", upValues, static async (context, cancellationToken) =>
             {
                 var upValues = context.GetCsClosure()!.UpValues;
                 var file = upValues[0].Read<FileHandle>();
-                var formats = upValues.AsSpan(1);
+                var formats = upValues.AsMemory(1);
                 var stack = context.Thread.Stack;
                 context.Return();
-                var resultCount = IOHelper.Read(context.Thread, file, "lines", 0, formats, true);
+                var resultCount = await IOHelper.ReadAsync(context.Thread, file, "lines", 0, formats, true, cancellationToken);
                 if (resultCount > 0 && stack.Get(context.ReturnFrameBase).Type is LuaValueType.Nil)
                 {
                     file.Close();
                 }
 
-                return new(resultCount);
+                return resultCount;
             })));
         }
     }
@@ -158,21 +158,20 @@ public sealed class IOLibrary
         }
         else
         {
-            var stream = File.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite);
+            var stream = context.State.FileManager.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite);
             var handle = new FileHandle(stream);
             io["stdout"] = new(handle);
             return new(context.Return(new LuaValue(handle)));
         }
     }
 
-    public ValueTask<int> Read(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
+    public async ValueTask<int> Read(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
         var file = context.State.Registry["stdin"].Read<FileHandle>();
         context.Return();
-        var stack = context.Thread.Stack;
 
-        var resultCount = IOHelper.Read(context.Thread, file, "read", 0, context.Arguments, false);
-        return new(resultCount);
+        var resultCount = await IOHelper.ReadAsync(context.Thread, file, "read", 0, context.Arguments.ToArray(), false, cancellationToken);
+        return resultCount;
     }
 
     public ValueTask<int> Type(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
@@ -189,10 +188,10 @@ public sealed class IOLibrary
         }
     }
 
-    public ValueTask<int> Write(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
+    public async ValueTask<int> Write(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
         var file = context.State.Registry["stdout"].Read<FileHandle>();
-        var resultCount = IOHelper.Write(file, "write", context);
-        return new(resultCount);
+        var resultCount = await IOHelper.WriteAsync(file, "write", context, cancellationToken);
+        return resultCount;
     }
 }

+ 10 - 10
src/Lua/Standard/Internal/IOHelper.cs

@@ -24,7 +24,7 @@ internal static class IOHelper
 
         try
         {
-            var stream = File.Open(fileName, fileMode, fileAccess);
+            var stream = thread.State.FileManager.Open(fileName, fileMode, fileAccess);
             thread.Stack.Push(new LuaValue(new FileHandle(stream)));
             return 1;
         }
@@ -44,7 +44,7 @@ internal static class IOHelper
 
     // TODO: optimize (use IBuffertWrite<byte>, async)
 
-    public static int Write(FileHandle file, string name, LuaFunctionExecutionContext context)
+    public static async ValueTask<int> WriteAsync(FileHandle file, string name, LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
         try
         {
@@ -53,14 +53,14 @@ internal static class IOHelper
                 var arg = context.Arguments[i];
                 if (arg.TryRead<string>(out var str))
                 {
-                    file.Write(str);
+                    await file.WriteAsync(str.AsMemory(), cancellationToken);
                 }
                 else if (arg.TryRead<double>(out var d))
                 {
                     using var fileBuffer = new PooledArray<char>(64);
                     var span = fileBuffer.AsSpan();
                     d.TryFormat(span, out var charsWritten);
-                    file.Write(span[..charsWritten]);
+                    await file.WriteAsync(fileBuffer.AsMemory()[..charsWritten], cancellationToken);
                 }
                 else
                 {
@@ -85,7 +85,7 @@ internal static class IOHelper
 
     static readonly LuaValue[] defaultReadFormat = ["*l"];
 
-    public static int Read(LuaThread thread, FileHandle file, string name, int startArgumentIndex, ReadOnlySpan<LuaValue> formats, bool throwError)
+    public static async ValueTask<int> ReadAsync(LuaThread thread, FileHandle file, string name, int startArgumentIndex, ReadOnlyMemory<LuaValue> formats, bool throwError, CancellationToken cancellationToken)
     {
         if (formats.Length == 0)
         {
@@ -99,7 +99,7 @@ internal static class IOHelper
         {
             for (int i = 0; i < formats.Length; i++)
             {
-                var format = formats[i];
+                var format = formats.Span[i];
                 if (format.TryRead<string>(out var str))
                 {
                     switch (str)
@@ -110,15 +110,15 @@ internal static class IOHelper
                             throw new NotImplementedException();
                         case "*a":
                         case "*all":
-                            stack.Push(file.ReadToEnd());
+                            stack.Push(await file.ReadToEndAsync(cancellationToken));
                             break;
                         case "*l":
                         case "*line":
-                            stack.Push(file.ReadLine() ?? LuaValue.Nil);
+                            stack.Push(await file.ReadLineAsync(cancellationToken) ?? LuaValue.Nil);
                             break;
                         case "L":
                         case "*L":
-                            var text = file.ReadLine();
+                            var text = await file.ReadLineAsync(cancellationToken);
                             stack.Push(text == null ? LuaValue.Nil : text + Environment.NewLine);
                             break;
                     }
@@ -129,7 +129,7 @@ internal static class IOHelper
 
                     for (int j = 0; j < count; j++)
                     {
-                        var b = file.ReadByte();
+                        var b = await file.ReadByteAsync(cancellationToken);
                         if (b == -1)
                         {
                             stack.PopUntil(top);

+ 1 - 1
src/Lua/Standard/ModuleLibrary.cs

@@ -71,7 +71,7 @@ public sealed class ModuleLibrary
         {
             path = pathSpan[..nextIndex].ToString();
             var fileName = path.Replace("?", name);
-            if (File.Exists(fileName))
+            if (state.FileManager.IsReadable(fileName))
             {
                 return fileName;
             }

+ 2 - 2
src/Lua/Standard/OperatingSystemLibrary.cs

@@ -143,7 +143,7 @@ public sealed class OperatingSystemLibrary
         var fileName = context.GetArgument<string>(0);
         try
         {
-            File.Delete(fileName);
+            context.State.FileManager.Remove(fileName);
             return new(context.Return(true));
         }
         catch (IOException ex)
@@ -158,7 +158,7 @@ public sealed class OperatingSystemLibrary
         var newName = context.GetArgument<string>(1);
         try
         {
-            File.Move(oldName, newName);
+            context.State.FileManager.Rename(oldName, newName);
             return new(context.Return(true));
         }
         catch (IOException ex)