Browse Source

Major refactoring of LuaPlatform

Akeit0 6 months ago
parent
commit
953d5d5f6a

+ 96 - 0
src/Lua/IO/CompositeLoaderFileSystem.cs

@@ -0,0 +1,96 @@
+namespace Lua.IO;
+
+public class CompositeLoaderFileSystem(ILuaFileLoader[] loaders, ILuaFileSystem? system = null) : ILuaFileSystem
+{
+    public static CompositeLoaderFileSystem Create(ILuaFileLoader[] loaders, ILuaFileSystem? system)
+    {
+        if (loaders == null || loaders.Length == 0)
+        {
+            throw new ArgumentException("Loaders cannot be null or empty", nameof(loaders));
+        }
+
+        return new(loaders, system);
+    }
+
+    public static CompositeLoaderFileSystem Create(params ILuaFileLoader[] loaders)
+    {
+        return new(loaders);
+    }
+
+    private (int index, string path)? cached;
+
+    public bool IsReadable(string path)
+    {
+        for (int index = 0; index < loaders.Length; index++)
+        {
+            ILuaFileLoader? loader = loaders[index];
+            if (loader.Exists(path))
+            {
+                cached = (index, path);
+                return true;
+            }
+        }
+
+        if (system != null)
+        {
+            cached = (loaders.Length, path);
+            return system.IsReadable(path);
+        }
+
+        return false;
+    }
+
+    public async ValueTask<ILuaStream> Open(string path, LuaFileMode mode, CancellationToken cancellationToken)
+    {
+        if (cached != null)
+        {
+            var cachedValue = cached.Value;
+            if (path == cachedValue.path)
+            {
+                if (cachedValue.index < loaders.Length)
+                {
+                    if (mode.CanWrite())
+                        throw new NotSupportedException("Cannot write to a file opened with a loader.");
+                    return ILuaStream.CreateFromFileContent(await loaders[cachedValue.index].LoadAsync(path, cancellationToken));
+                }
+            }
+        }
+        else
+        {
+            foreach (var loader in loaders)
+            {
+                if (loader.Exists(path))
+                {
+                    if (mode.CanWrite())
+                        throw new NotSupportedException("Cannot write to a file opened with a loader.");
+                    return ILuaStream.CreateFromFileContent(await loader.LoadAsync(path, cancellationToken));
+                }
+            }
+        }
+
+
+        return system != null ? await system.Open(path, mode, cancellationToken) : throw new NotSupportedException();
+    }
+
+    public ValueTask Rename(string oldName, string newName, CancellationToken cancellationToken)
+    {
+        return system?.Rename(oldName, newName, cancellationToken) ?? throw new NotSupportedException();
+    }
+
+    public ValueTask Remove(string path, CancellationToken cancellationToken)
+    {
+        return system?.Remove(path, cancellationToken) ?? throw new NotSupportedException();
+    }
+
+    public string DirectorySeparator => system?.DirectorySeparator ?? "/";
+
+    public string GetTempFileName()
+    {
+        return system?.GetTempFileName() ?? throw new NotSupportedException();
+    }
+
+    public ValueTask<ILuaStream> OpenTempFileStream(CancellationToken cancellationToken)
+    {
+        return system?.OpenTempFileStream(cancellationToken) ?? throw new NotSupportedException();
+    }
+}

+ 11 - 15
src/Lua/IO/FileSystem.cs

@@ -58,35 +58,31 @@
             return wrapper;
             return wrapper;
         }
         }
 
 
-        public ILuaStream Open(string path, LuaFileMode mode)
+        public ValueTask<ILuaStream> Open(string path, LuaFileMode mode, CancellationToken cancellationToken)
         {
         {
-            if (mode is LuaFileMode.ReadBinaryOrText)
+            if (mode is LuaFileMode.Load)
             {
             {
-                return new LuaChunkStream(File.OpenRead(path));
+                return new ( new LuaChunkStream(File.OpenRead(path)));
             }
             }
 
 
             var openMode = mode.GetOpenMode();
             var openMode = mode.GetOpenMode();
             var contentType = mode.GetContentType();
             var contentType = mode.GetContentType();
-            return Open(path, openMode, contentType);
+            return new(Open(path, openMode, contentType));
         }
         }
 
 
-        public ILuaStream Open(string path, string mode)
+        public ValueTask Rename(string oldName, string newName,CancellationToken cancellationToken)
         {
         {
-            var flags = LuaFileModeExtensions.ParseModeString(mode);
-            return Open(path, flags);
-        }
-
-        public void Rename(string oldName, string newName)
-        {
-            if (oldName == newName) return;
+            if (oldName == newName) return default;
             if (File.Exists(newName)) File.Delete(newName);
             if (File.Exists(newName)) File.Delete(newName);
             File.Move(oldName, newName);
             File.Move(oldName, newName);
             File.Delete(oldName);
             File.Delete(oldName);
+            return default;
         }
         }
 
 
-        public void Remove(string path)
+        public ValueTask Remove(string path,CancellationToken cancellationToken)
         {
         {
             File.Delete(path);
             File.Delete(path);
+            return default;
         }
         }
 
 
         static readonly string directorySeparator = Path.DirectorySeparatorChar.ToString();
         static readonly string directorySeparator = Path.DirectorySeparatorChar.ToString();
@@ -97,9 +93,9 @@
             return Path.GetTempFileName();
             return Path.GetTempFileName();
         }
         }
 
 
-        public ILuaStream OpenTempFileStream()
+        public ValueTask<ILuaStream> OpenTempFileStream(CancellationToken cancellationToken)
         {
         {
-            return new TextLuaStream(LuaFileMode.ReadUpdateText, File.Open(Path.GetTempFileName(), FileMode.Open, FileAccess.ReadWrite));
+            return new( new TextLuaStream(LuaFileMode.ReadUpdateText, File.Open(Path.GetTempFileName(), FileMode.Open, FileAccess.ReadWrite)));
         }
         }
     }
     }
 }
 }

+ 7 - 0
src/Lua/IO/ILuaFileLoader.cs

@@ -0,0 +1,7 @@
+namespace Lua.IO;
+
+public interface ILuaFileLoader
+{
+    public bool Exists(string path);
+    public ValueTask<LuaFileContent> LoadAsync(string path, CancellationToken cancellationToken = default);
+}

+ 5 - 5
src/Lua/IO/ILuaFileSystem.cs

@@ -3,10 +3,10 @@
 public interface ILuaFileSystem
 public interface ILuaFileSystem
 {
 {
     public bool IsReadable(string path);
     public bool IsReadable(string path);
-    public ILuaStream Open(string path, LuaFileMode mode);
-    public void Rename(string oldName, string newName);
-    public void Remove(string path);
-    public string DirectorySeparator { get; }
+    public ValueTask<ILuaStream> Open(string path, LuaFileMode mode, CancellationToken cancellationToken);
+    public ValueTask Rename(string oldName, string newName, CancellationToken cancellationToken);
+    public ValueTask Remove(string path, CancellationToken cancellationToken);
+    public string DirectorySeparator => "/";
     public string GetTempFileName();
     public string GetTempFileName();
-    public ILuaStream OpenTempFileStream();
+    public ValueTask<ILuaStream> OpenTempFileStream(CancellationToken cancellationToken);
 }
 }

+ 12 - 0
src/Lua/IO/ILuaStream.cs

@@ -69,6 +69,18 @@
                 ? new BinaryLuaStream(mode, stream)
                 ? new BinaryLuaStream(mode, stream)
                 : new TextLuaStream(mode, stream);
                 : new TextLuaStream(mode, stream);
         }
         }
+        
+        public static ILuaStream CreateFromFileContent(LuaFileContent content)
+        {
+            if (content.Type == LuaFileContentType.Binary)
+            {
+                return new ByteMemoryStream(content.ReadBytes() );
+            }
+            else
+            {
+                return new StringStream(content.ReadString());
+            }
+        }
 
 
 
 
         public void Close()
         public void Close()

+ 7 - 1
src/Lua/IO/LuaFileMode.cs

@@ -1,5 +1,10 @@
 namespace Lua.IO;
 namespace Lua.IO;
-
+public enum LuaFileContentTypeFlags
+{
+    None = 0,
+    Binary = 1 << 0, // b
+    Text = 1 << 1, // t (default if neither specified)
+}
 [Flags]
 [Flags]
 public enum LuaFileMode
 public enum LuaFileMode
 {
 {
@@ -38,6 +43,7 @@ public enum LuaFileMode
     /// This is used for load files. bt mode.
     /// This is used for load files. bt mode.
     /// </summary>
     /// </summary>
     ReadBinaryOrText = Read | Binary | Text, //(default is text, but can be binary)
     ReadBinaryOrText = Read | Binary | Text, //(default is text, but can be binary)
+    Load = ReadBinaryOrText, // Mode for loading files to compile Lua scripts
 }
 }
 
 
 public static class LuaFileModeExtensions
 public static class LuaFileModeExtensions

+ 7 - 1
src/Lua/IO/StandardIOStream.cs

@@ -28,9 +28,15 @@
         public long Seek(long offset, SeekOrigin origin)
         public long Seek(long offset, SeekOrigin origin)
             => innerStream.Seek(offset, origin);
             => innerStream.Seek(offset, origin);
 
 
-        public void Dispose()
+        public void Close()
         {
         {
             throw new IOException("cannot close standard file");
             throw new IOException("cannot close standard file");
         }
         }
+
+        public void Dispose()
+        {
+            // Do not dispose inner stream to prevent closing standard IO streams
+            innerStream.Dispose();
+        }
     }
     }
 }
 }

+ 7 - 12
src/Lua/LuaState.cs

@@ -37,14 +37,14 @@ public sealed class LuaState
     public LuaMainThread MainThread => mainThread;
     public LuaMainThread MainThread => mainThread;
 
 
     public LuaThreadAccess RootAccess => new(mainThread, 0);
     public LuaThreadAccess RootAccess => new(mainThread, 0);
-    
-    public  ILuaPlatform Platform { get; }
+
+    public LuaPlatform Platform { get; }
 
 
     public ILuaModuleLoader ModuleLoader { get; set; } = FileModuleLoader.Instance;
     public ILuaModuleLoader ModuleLoader { get; set; } = FileModuleLoader.Instance;
 
 
-    public ILuaFileSystem FileSystem => Platform.FileSystem ?? throw new InvalidOperationException("FileSystem is not set. Please set it before using LuaState.");
+    public ILuaFileSystem FileSystem => Platform.FileSystem ?? throw new InvalidOperationException("FileSystem is not set. Please set it before access.");
 
 
-    public ILuaOsEnvironment OsEnvironment   => Platform.OsEnvironment ?? throw new InvalidOperationException("OperatingSystem is not set. Please set it before using LuaState.");
+    public ILuaOsEnvironment OsEnvironment => Platform.OsEnvironment ?? throw new InvalidOperationException("OperatingSystem is not set. Please set it before access.");
 
 
     public ILuaStandardIO StandardIO => Platform.StandardIO;
     public ILuaStandardIO StandardIO => Platform.StandardIO;
 
 
@@ -56,18 +56,13 @@ public sealed class LuaState
     LuaTable? functionMetatable;
     LuaTable? functionMetatable;
     LuaTable? threadMetatable;
     LuaTable? threadMetatable;
 
 
-    public static LuaState Create()
-    {
-        return Create(LuaPlatform.Default);
-    }
-    
-    public static LuaState Create(ILuaPlatform platform)
+    public static LuaState Create(LuaPlatform? platform = null)
     {
     {
-        var state = new LuaState(platform);
+        var state = new LuaState(platform ?? LuaPlatform.Default);
         return state;
         return state;
     }
     }
 
 
-    LuaState(ILuaPlatform platform)
+    LuaState(LuaPlatform platform)
     {
     {
         mainThread = new(this);
         mainThread = new(this);
         environment = new();
         environment = new();

+ 4 - 3
src/Lua/LuaStateExtensions.cs

@@ -39,20 +39,21 @@ public static class LuaStateExtensions
     {
     {
         var name = "@" + fileName;
         var name = "@" + fileName;
         LuaClosure closure;
         LuaClosure closure;
-        
+
         var openFlags = LuaFileMode.Read;
         var openFlags = LuaFileMode.Read;
         if (mode.Contains('b'))
         if (mode.Contains('b'))
         {
         {
             openFlags |= LuaFileMode.Binary;
             openFlags |= LuaFileMode.Binary;
         }
         }
+
         if (mode.Contains('t'))
         if (mode.Contains('t'))
         {
         {
             openFlags |= LuaFileMode.Text;
             openFlags |= LuaFileMode.Text;
         }
         }
 
 
-        using var stream = state.FileSystem.Open(fileName, openFlags);
+        using var stream = await state.FileSystem.Open(fileName, openFlags, cancellationToken);
         var content = await stream.ReadAllAsync(cancellationToken);
         var content = await stream.ReadAllAsync(cancellationToken);
-            
+
         if (content.Type == LuaFileContentType.Binary)
         if (content.Type == LuaFileContentType.Binary)
         {
         {
             closure = state.Load(content.ReadBytes().Span, name, mode, environment);
             closure = state.Load(content.ReadBytes().Span, name, mode, environment);

+ 0 - 25
src/Lua/Platforms/ILuaPlatform.cs

@@ -1,25 +0,0 @@
-using Lua.IO;
-
-namespace Lua.Platforms;
-
-/// <summary>
-/// Represents a complete platform configuration for Lua execution.
-/// Provides all platform-specific implementations in one cohesive package.
-/// </summary>
-public interface ILuaPlatform
-{
-    /// <summary>
-    /// Gets the file system implementation for this platform
-    /// </summary>
-    ILuaFileSystem FileSystem { get; }
-    
-    /// <summary>
-    /// Gets the operating system environment abstraction for this platform
-    /// </summary>
-    ILuaOsEnvironment OsEnvironment { get; }
-    
-    /// <summary>
-    /// Gets the standard I/O implementation for this platform
-    /// </summary>
-    ILuaStandardIO StandardIO { get; }
-}

+ 12 - 8
src/Lua/Platforms/LuaPlatform.cs

@@ -6,17 +6,21 @@ namespace Lua.Platforms;
 /// <summary>
 /// <summary>
 ///  Platform abstraction for Lua.
 ///  Platform abstraction for Lua.
 /// </summary>
 /// </summary>
-/// <param name="FileSystem"></param>
-/// <param name="OsEnvironment"></param>
-/// <param name="StandardIO"></param>
-public sealed record LuaPlatform(ILuaFileSystem FileSystem , ILuaOsEnvironment OsEnvironment,ILuaStandardIO StandardIO): ILuaPlatform
+/// <param name="fileSystem"></param>
+/// <param name="osEnvironment"></param>
+/// <param name="standardIO"></param>
+public sealed class LuaPlatform(ILuaFileSystem fileSystem, ILuaOsEnvironment osEnvironment, ILuaStandardIO standardIO)
 {
 {
     /// <summary>
     /// <summary>
     /// Standard console platform implementation.
     /// Standard console platform implementation.
     /// Uses real file system, console I/O, and system operations.
     /// Uses real file system, console I/O, and system operations.
     /// </summary>
     /// </summary>
-    public static  LuaPlatform Default => new( 
-        FileSystem: new FileSystem(),
-        OsEnvironment: new SystemOsEnvironment(),
-        StandardIO:  new ConsoleStandardIO());
+    public static LuaPlatform Default => new(
+        fileSystem: new FileSystem(),
+        osEnvironment: new SystemOsEnvironment(),
+        standardIO: new ConsoleStandardIO());
+
+    public ILuaFileSystem FileSystem { get; set; } = fileSystem;
+    public ILuaOsEnvironment OsEnvironment { get; set; } = osEnvironment;
+    public ILuaStandardIO StandardIO { get; set; } = standardIO;
 }
 }

+ 1 - 1
src/Lua/Platforms/SystemOsEnvironment.cs

@@ -12,7 +12,7 @@ namespace Lua.Platforms
             return Environment.GetEnvironmentVariable(name);
             return Environment.GetEnvironmentVariable(name);
         }
         }
 
 
-        public ValueTask Exit(int exitCode ,CancellationToken cancellationToken)
+        public ValueTask Exit(int exitCode, CancellationToken cancellationToken)
         {
         {
             Environment.Exit(exitCode);
             Environment.Exit(exitCode);
             return default;
             return default;

+ 22 - 22
src/Lua/Standard/IOLibrary.cs

@@ -60,36 +60,36 @@ public sealed class IOLibrary
         }
         }
     }
     }
 
 
-    public ValueTask<int> Input(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
+    public async ValueTask<int> Input(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
         var registry = context.State.Registry;
         var registry = context.State.Registry;
 
 
         if (context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil)
         if (context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil)
         {
         {
-            return new(context.Return(registry["_IO_input"]));
+            return context.Return(registry["_IO_input"]);
         }
         }
 
 
         var arg = context.Arguments[0];
         var arg = context.Arguments[0];
         if (arg.TryRead<FileHandle>(out var file))
         if (arg.TryRead<FileHandle>(out var file))
         {
         {
             registry["_IO_input"] = new(file);
             registry["_IO_input"] = new(file);
-            return new(context.Return(new LuaValue(file)));
+            return context.Return(new LuaValue(file));
         }
         }
         else
         else
         {
         {
-            var stream = context.State.FileSystem.Open(arg.ToString(), LuaFileMode.ReadUpdateText);
+            var stream =await context.State.FileSystem.Open(arg.ToString(), LuaFileMode.ReadUpdateText, cancellationToken);
             var handle = new FileHandle(stream);
             var handle = new FileHandle(stream);
             registry["_IO_input"] = new(handle);
             registry["_IO_input"] = new(handle);
-            return new(context.Return(new LuaValue(handle)));
+            return context.Return(new LuaValue(handle));
         }
         }
     }
     }
 
 
-    public ValueTask<int> Lines(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
+    public async ValueTask<int> Lines(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
         if (context.ArgumentCount == 0)
         if (context.ArgumentCount == 0)
         {
         {
             var file = context.State.Registry["_IO_input"].Read<FileHandle>();
             var file = context.State.Registry["_IO_input"].Read<FileHandle>();
-            return new(context.Return(new CSharpClosure("iterator", [new(file)], static async (context, cancellationToken) =>
+            return context.Return(new CSharpClosure("iterator", [new(file)], static async (context, cancellationToken) =>
             {
             {
                 var file = context.GetCsClosure()!.UpValues[0].Read<FileHandle>();
                 var file = context.GetCsClosure()!.UpValues[0].Read<FileHandle>();
                 context.Return();
                 context.Return();
@@ -100,7 +100,7 @@ public sealed class IOLibrary
                 }
                 }
 
 
                 return resultCount;
                 return resultCount;
-            })));
+            }));
         }
         }
         else
         else
         {
         {
@@ -108,14 +108,14 @@ public sealed class IOLibrary
             var stack = context.Thread.Stack;
             var stack = context.Thread.Stack;
             context.Return();
             context.Return();
 
 
-            IOHelper.Open(context.Thread, fileName, "r", true);
+            await IOHelper.Open(context.Thread, fileName, "r", true,cancellationToken);
 
 
             var file = stack.Get(context.ReturnFrameBase).Read<FileHandle>();
             var file = stack.Get(context.ReturnFrameBase).Read<FileHandle>();
             var upValues = new LuaValue[context.Arguments.Length];
             var upValues = new LuaValue[context.Arguments.Length];
             upValues[0] = new(file);
             upValues[0] = new(file);
             context.Arguments[1..].CopyTo(upValues[1..]);
             context.Arguments[1..].CopyTo(upValues[1..]);
 
 
-            return new(context.Return(new CSharpClosure("iterator", upValues, static async (context, cancellationToken) =>
+            return context.Return(new CSharpClosure("iterator", upValues, static async (context, cancellationToken) =>
             {
             {
                 var upValues = context.GetCsClosure()!.UpValues;
                 var upValues = context.GetCsClosure()!.UpValues;
                 var file = upValues[0].Read<FileHandle>();
                 var file = upValues[0].Read<FileHandle>();
@@ -129,11 +129,11 @@ public sealed class IOLibrary
                 }
                 }
 
 
                 return resultCount;
                 return resultCount;
-            })));
+            }));
         }
         }
     }
     }
 
 
-    public ValueTask<int> Open(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
+    public async ValueTask<int> Open(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
         var fileName = context.GetArgument<string>(0);
         var fileName = context.GetArgument<string>(0);
         var mode = context.HasArgument(1)
         var mode = context.HasArgument(1)
@@ -142,36 +142,36 @@ public sealed class IOLibrary
         context.Return();
         context.Return();
         try
         try
         {
         {
-            var resultCount = IOHelper.Open(context.Thread, fileName, mode, true);
-            return new(resultCount);
+            var resultCount = await IOHelper.Open(context.Thread, fileName, mode, true, cancellationToken);
+            return resultCount;
         }
         }
         catch (IOException ex)
         catch (IOException ex)
         {
         {
-            return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult));
+            return context.Return(LuaValue.Nil, ex.Message, ex.HResult);
         }
         }
     }
     }
 
 
-    public ValueTask<int> Output(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
+    public async ValueTask<int> Output(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
         var io = context.State.Registry;
         var io = context.State.Registry;
 
 
         if (context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil)
         if (context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil)
         {
         {
-            return new(context.Return(io["_IO_output"]));
+            return context.Return(io["_IO_output"]);
         }
         }
 
 
         var arg = context.Arguments[0];
         var arg = context.Arguments[0];
         if (arg.TryRead<FileHandle>(out var file))
         if (arg.TryRead<FileHandle>(out var file))
         {
         {
             io["_IO_output"] = new(file);
             io["_IO_output"] = new(file);
-            return new(context.Return(new LuaValue(file)));
+            return context.Return(new LuaValue(file));
         }
         }
         else
         else
         {
         {
-            var stream = context.State.FileSystem.Open(arg.ToString(), LuaFileMode.WriteUpdateText);
+            var stream = await context.State.FileSystem.Open(arg.ToString(), LuaFileMode.WriteUpdateText, cancellationToken);
             var handle = new FileHandle(stream);
             var handle = new FileHandle(stream);
             io["_IO_output"] = new(handle);
             io["_IO_output"] = new(handle);
-            return new(context.Return(new LuaValue(handle)));
+            return context.Return(new LuaValue(handle));
         }
         }
     }
     }
 
 
@@ -206,8 +206,8 @@ public sealed class IOLibrary
         return resultCount;
         return resultCount;
     }
     }
 
 
-    public ValueTask<int> TmpFile(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
+    public async ValueTask<int> TmpFile(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
-        return new(context.Return(LuaValue.FromUserData(new FileHandle(context.State.FileSystem.OpenTempFileStream()))));
+        return context.Return(LuaValue.FromUserData(new FileHandle(await context.State.FileSystem.OpenTempFileStream( cancellationToken))));
     }
     }
 }
 }

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

@@ -6,13 +6,13 @@ namespace Lua.Standard.Internal;
 
 
 internal static class IOHelper
 internal static class IOHelper
 {
 {
-    public static int Open(LuaThread thread, string fileName, string mode, bool throwError)
+    public static async ValueTask<int> Open(LuaThread thread, string fileName, string mode, bool throwError,CancellationToken cancellationToken)
     {
     {
         var fileMode = LuaFileModeExtensions.ParseModeString(mode);
         var fileMode = LuaFileModeExtensions.ParseModeString(mode);
         if (!fileMode.IsValid()) throw new LuaRuntimeException(thread, "bad argument #2 to 'open' (invalid mode)");
         if (!fileMode.IsValid()) throw new LuaRuntimeException(thread, "bad argument #2 to 'open' (invalid mode)");
         try
         try
         {
         {
-            var stream = thread.State.FileSystem.Open(fileName, fileMode);
+            var stream =await thread.State.FileSystem.Open(fileName, fileMode,cancellationToken);
 
 
             thread.Stack.Push(new LuaValue(new FileHandle(stream)));
             thread.Stack.Push(new LuaValue(new FileHandle(stream)));
             return 1;
             return 1;

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

@@ -56,7 +56,7 @@ public sealed class OperatingSystemLibrary
         else
         else
         {
         {
             var offset = context.State.OsEnvironment.GetLocalTimeZoneOffset();
             var offset = context.State.OsEnvironment.GetLocalTimeZoneOffset();
-            now = now.Add(offset);
+            now += offset;
             isDst = now.IsDaylightSavingTime();
             isDst = now.IsDaylightSavingTime();
         }
         }
 
 
@@ -70,7 +70,7 @@ public sealed class OperatingSystemLibrary
                 ["hour"] = now.Hour,
                 ["hour"] = now.Hour,
                 ["min"] = now.Minute,
                 ["min"] = now.Minute,
                 ["sec"] = now.Second,
                 ["sec"] = now.Second,
-                ["wday"] = ((int)now.DayOfWeek) + 1,
+                ["wday"] = (int)now.DayOfWeek + 1,
                 ["yday"] = now.DayOfYear,
                 ["yday"] = now.DayOfYear,
                 ["isdst"] = isDst
                 ["isdst"] = isDst
             };
             };
@@ -136,32 +136,32 @@ public sealed class OperatingSystemLibrary
         return new(context.Return(context.State.OsEnvironment.GetEnvironmentVariable(variable) ?? LuaValue.Nil));
         return new(context.Return(context.State.OsEnvironment.GetEnvironmentVariable(variable) ?? LuaValue.Nil));
     }
     }
 
 
-    public ValueTask<int> Remove(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
+    public async ValueTask<int> Remove(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
         var fileName = context.GetArgument<string>(0);
         var fileName = context.GetArgument<string>(0);
         try
         try
         {
         {
-            context.State.FileSystem.Remove(fileName);
-            return new(context.Return(true));
+            await context.State.FileSystem.Remove(fileName, cancellationToken);
+            return context.Return(true);
         }
         }
         catch (IOException ex)
         catch (IOException ex)
         {
         {
-            return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult));
+            return context.Return(LuaValue.Nil, ex.Message, ex.HResult);
         }
         }
     }
     }
 
 
-    public ValueTask<int> Rename(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
+    public async ValueTask<int> Rename(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
         var oldName = context.GetArgument<string>(0);
         var oldName = context.GetArgument<string>(0);
         var newName = context.GetArgument<string>(1);
         var newName = context.GetArgument<string>(1);
         try
         try
         {
         {
-            context.State.FileSystem.Rename(oldName, newName);
-            return new(context.Return(true));
+            await context.State.FileSystem.Rename(oldName, newName, cancellationToken);
+            return context.Return(true);
         }
         }
         catch (IOException ex)
         catch (IOException ex)
         {
         {
-            return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult));
+            return context.Return(LuaValue.Nil, ex.Message, ex.HResult);
         }
         }
     }
     }
 
 

+ 10 - 4
tests/Lua.Tests/AbstractFileTests.cs

@@ -9,7 +9,7 @@ public class AbstractFileTests
 {
 {
     class ReadOnlyFileSystem(Dictionary<string, string> dictionary) : NotImplementedExceptionFileSystemBase
     class ReadOnlyFileSystem(Dictionary<string, string> dictionary) : NotImplementedExceptionFileSystemBase
     {
     {
-        public override ILuaStream Open(string path, LuaFileMode mode)
+        public override  ValueTask<ILuaStream> Open(string path, LuaFileMode mode,CancellationToken cancellationToken)
         {
         {
             if (!dictionary.TryGetValue(path, out var value))
             if (!dictionary.TryGetValue(path, out var value))
             {
             {
@@ -18,7 +18,7 @@ public class AbstractFileTests
 
 
             if (mode != LuaFileMode.ReadText)
             if (mode != LuaFileMode.ReadText)
                 throw new IOException($"File {path} not opened in read mode");
                 throw new IOException($"File {path} not opened in read mode");
-            return new ReadOnlyCharMemoryLuaIOStream(value.AsMemory());
+            return new (new ReadOnlyCharMemoryLuaIOStream(value.AsMemory()));
         }
         }
     }
     }
 
 
@@ -27,7 +27,10 @@ public class AbstractFileTests
     {
     {
         var fileContent = "line1\nline2\r\nline3";
         var fileContent = "line1\nline2\r\nline3";
         var fileSystem = new ReadOnlyFileSystem(new() { { "test.txt", fileContent } });
         var fileSystem = new ReadOnlyFileSystem(new() { { "test.txt", fileContent } });
-        var state = LuaState.Create(LuaPlatform.Default with{FileSystem = fileSystem});
+        var state = LuaState.Create(new(
+            fileSystem: fileSystem,
+            osEnvironment: null!,
+            standardIO: new ConsoleStandardIO()));
         state.OpenStandardLibraries();
         state.OpenStandardLibraries();
         try
         try
         {
         {
@@ -56,7 +59,10 @@ public class AbstractFileTests
     {
     {
         var fileContent = "Hello, World!";
         var fileContent = "Hello, World!";
         var fileSystem = new ReadOnlyFileSystem(new() { { "test.txt", fileContent } });
         var fileSystem = new ReadOnlyFileSystem(new() { { "test.txt", fileContent } });
-        var state = LuaState.Create(LuaPlatform.Default with{FileSystem = fileSystem});
+        var state = LuaState.Create(new(
+            fileSystem: fileSystem,
+            osEnvironment: null!,
+            standardIO: new ConsoleStandardIO()));
         state.OpenStandardLibraries();
         state.OpenStandardLibraries();
 
 
         await state.DoStringAsync(
         await state.DoStringAsync(

+ 4 - 9
tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs

@@ -9,22 +9,17 @@ namespace Lua.Tests.Helpers
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
-        public virtual ValueTask<LuaFileContent> ReadFileContentAsync(string fileName, CancellationToken cancellationToken)
+        public virtual ValueTask<ILuaStream> Open(string path, LuaFileMode mode, CancellationToken cancellationToken)
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
-        public virtual ILuaStream Open(string path, LuaFileMode mode)
+        public virtual ValueTask Rename(string oldName, string newName, CancellationToken cancellationToken)
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
-        public virtual void Rename(string oldName, string newName)
-        {
-            throw new NotImplementedException();
-        }
-
-        public virtual void Remove(string path)
+        public virtual ValueTask Remove(string path, CancellationToken cancellationToken)
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
@@ -36,7 +31,7 @@ namespace Lua.Tests.Helpers
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }
 
 
-        public ILuaStream OpenTempFileStream()
+        public ValueTask<ILuaStream> OpenTempFileStream(CancellationToken cancellationToken)
         {
         {
             throw new NotImplementedException();
             throw new NotImplementedException();
         }
         }

+ 29 - 29
tests/Lua.Tests/IOTests.cs

@@ -87,13 +87,13 @@ public class IOTests : IDisposable
         var testContent = "Hello, World!\nThis is a test.";
         var testContent = "Hello, World!\nThis is a test.";
 
 
         // Write text
         // Write text
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.WriteText,CancellationToken.None))
         {
         {
             await stream.WriteAsync(new(testContent), CancellationToken.None);
             await stream.WriteAsync(new(testContent), CancellationToken.None);
         }
         }
 
 
         // Read text
         // Read text
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.ReadText,CancellationToken.None))
         {
         {
             var content = await stream.ReadAllAsync(CancellationToken.None);
             var content = await stream.ReadAllAsync(CancellationToken.None);
             Assert.That(content.Type, Is.EqualTo(LuaFileContentType.Text));
             Assert.That(content.Type, Is.EqualTo(LuaFileContentType.Text));
@@ -108,13 +108,13 @@ public class IOTests : IDisposable
         var testBytes = new byte[] { 0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD };
         var testBytes = new byte[] { 0x00, 0x01, 0x02, 0xFF, 0xFE, 0xFD };
 
 
         // Write bytes
         // Write bytes
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteBinary))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.WriteBinary,CancellationToken.None))
         {
         {
             await stream.WriteAsync(new(testBytes), CancellationToken.None);
             await stream.WriteAsync(new(testBytes), CancellationToken.None);
         }
         }
 
 
         // Read bytes
         // Read bytes
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadBinary))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.ReadBinary,CancellationToken.None))
         {
         {
             var content = await stream.ReadAllAsync(CancellationToken.None);
             var content = await stream.ReadAllAsync(CancellationToken.None);
             Assert.That(content.Type, Is.EqualTo(LuaFileContentType.Binary));
             Assert.That(content.Type, Is.EqualTo(LuaFileContentType.Binary));
@@ -123,11 +123,11 @@ public class IOTests : IDisposable
     }
     }
 
 
     [Test]
     [Test]
-    public  void TextStream_Cannot_Write_Binary_Content()
+    public  async Task TextStream_Cannot_Write_Binary_Content()
     {
     {
         var testFile = GetTestFilePath("text_binary_mix.txt");
         var testFile = GetTestFilePath("text_binary_mix.txt");
 
 
-        using var stream = fileSystem.Open(testFile, LuaFileMode.WriteText);
+        using var stream = await fileSystem.Open(testFile, LuaFileMode.WriteText,CancellationToken.None);
         var binaryContent = new LuaFileContent(new byte[] { 0x00, 0x01 });
         var binaryContent = new LuaFileContent(new byte[] { 0x00, 0x01 });
 
 
         Assert.ThrowsAsync<InvalidOperationException>(
         Assert.ThrowsAsync<InvalidOperationException>(
@@ -136,11 +136,11 @@ public class IOTests : IDisposable
     }
     }
 
 
     [Test]
     [Test]
-    public void BinaryStream_Cannot_Write_Text_Content()
+    public async Task BinaryStream_Cannot_Write_Text_Content()
     {
     {
         var testFile = GetTestFilePath("binary_text_mix.bin");
         var testFile = GetTestFilePath("binary_text_mix.bin");
 
 
-        using var stream = fileSystem.Open(testFile, LuaFileMode.WriteBinary);
+        using var stream = await fileSystem.Open(testFile, LuaFileMode.WriteBinary,CancellationToken.None);
         var textContent = new LuaFileContent("Hello");
         var textContent = new LuaFileContent("Hello");
 
 
         Assert.ThrowsAsync<InvalidOperationException>(
         Assert.ThrowsAsync<InvalidOperationException>(
@@ -155,13 +155,13 @@ public class IOTests : IDisposable
         var lines = new[] { "Line 1", "Line 2", "Line 3" };
         var lines = new[] { "Line 1", "Line 2", "Line 3" };
 
 
         // Write multiple lines
         // Write multiple lines
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.WriteText,CancellationToken.None))
         {
         {
             await stream.WriteAsync(new(string.Join("\n", lines)), CancellationToken.None);
             await stream.WriteAsync(new(string.Join("\n", lines)), CancellationToken.None);
         }
         }
 
 
         // Read lines one by one
         // Read lines one by one
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.ReadText,CancellationToken.None))
         {
         {
             for (int i = 0; i < lines.Length; i++)
             for (int i = 0; i < lines.Length; i++)
             {
             {
@@ -182,13 +182,13 @@ public class IOTests : IDisposable
         var testContent = "Hello, World!";
         var testContent = "Hello, World!";
 
 
         // Write content
         // Write content
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.WriteText,CancellationToken.None))
         {
         {
             await stream.WriteAsync(new(testContent), CancellationToken.None);
             await stream.WriteAsync(new(testContent), CancellationToken.None);
         }
         }
 
 
         // Read partial strings
         // Read partial strings
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.ReadText,CancellationToken.None))
         {
         {
             var part1 = await stream.ReadStringAsync(5, CancellationToken.None);
             var part1 = await stream.ReadStringAsync(5, CancellationToken.None);
             Assert.That(part1, Is.EqualTo("Hello"));
             Assert.That(part1, Is.EqualTo("Hello"));
@@ -209,12 +209,12 @@ public class IOTests : IDisposable
     {
     {
         var testFile = GetTestFilePath("binary_no_text.bin");
         var testFile = GetTestFilePath("binary_no_text.bin");
 
 
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteBinary))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.WriteBinary,CancellationToken.None))
         {
         {
             await stream.WriteAsync(new(new byte[] { 0x01, 0x02 }), CancellationToken.None);
             await stream.WriteAsync(new(new byte[] { 0x01, 0x02 }), CancellationToken.None);
         }
         }
 
 
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadBinary))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.ReadBinary,CancellationToken.None))
         {
         {
             Assert.ThrowsAsync<InvalidOperationException>(
             Assert.ThrowsAsync<InvalidOperationException>(
                 async () => await stream.ReadLineAsync(CancellationToken.None)
                 async () => await stream.ReadLineAsync(CancellationToken.None)
@@ -232,19 +232,19 @@ public class IOTests : IDisposable
         var testFile = GetTestFilePath("append_test.txt");
         var testFile = GetTestFilePath("append_test.txt");
 
 
         // Write initial content
         // Write initial content
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.WriteText,CancellationToken.None))
         {
         {
             await stream.WriteAsync(new("Hello"), CancellationToken.None);
             await stream.WriteAsync(new("Hello"), CancellationToken.None);
         }
         }
 
 
         // Append content
         // Append content
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.AppendText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.AppendText,CancellationToken.None))
         {
         {
             await stream.WriteAsync(new(" World"), CancellationToken.None);
             await stream.WriteAsync(new(" World"), CancellationToken.None);
         }
         }
 
 
         // Read and verify
         // Read and verify
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.ReadText,CancellationToken.None))
         {
         {
             var content = await stream.ReadAllAsync(CancellationToken.None);
             var content = await stream.ReadAllAsync(CancellationToken.None);
             Assert.That(content.ReadString(), Is.EqualTo("Hello World"));
             Assert.That(content.ReadString(), Is.EqualTo("Hello World"));
@@ -258,13 +258,13 @@ public class IOTests : IDisposable
         var testContent = "0123456789";
         var testContent = "0123456789";
 
 
         // Write content
         // Write content
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.WriteText,CancellationToken.None))
         {
         {
             await stream.WriteAsync(new(testContent), CancellationToken.None);
             await stream.WriteAsync(new(testContent), CancellationToken.None);
         }
         }
 
 
         // Test seeking
         // Test seeking
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.ReadText,CancellationToken.None))
         {
         {
             // Seek from beginning
             // Seek from beginning
             stream.Seek(5, SeekOrigin.Begin);
             stream.Seek(5, SeekOrigin.Begin);
@@ -284,14 +284,14 @@ public class IOTests : IDisposable
     }
     }
 
 
     [Test]
     [Test]
-    public void FileSystem_Rename_Works()
+    public async Task FileSystem_Rename_Works()
     {
     {
         var oldPath = GetTestFilePath("old_name.txt");
         var oldPath = GetTestFilePath("old_name.txt");
         var newPath = GetTestFilePath("new_name.txt");
         var newPath = GetTestFilePath("new_name.txt");
 
 
         File.WriteAllText(oldPath, "test content");
         File.WriteAllText(oldPath, "test content");
 
 
-        fileSystem.Rename(oldPath, newPath);
+       await fileSystem.Rename(oldPath, newPath,CancellationToken.None);
 
 
         Assert.That(File.Exists(oldPath), Is.False);
         Assert.That(File.Exists(oldPath), Is.False);
         Assert.That(File.Exists(newPath), Is.True);
         Assert.That(File.Exists(newPath), Is.True);
@@ -299,14 +299,14 @@ public class IOTests : IDisposable
     }
     }
 
 
     [Test]
     [Test]
-    public void FileSystem_Remove_Works()
+    public async Task FileSystem_Remove_Works()
     {
     {
         var testFile = GetTestFilePath("remove_test.txt");
         var testFile = GetTestFilePath("remove_test.txt");
 
 
         File.WriteAllText(testFile, "test content");
         File.WriteAllText(testFile, "test content");
         Assert.That(File.Exists(testFile), Is.True);
         Assert.That(File.Exists(testFile), Is.True);
 
 
-        fileSystem.Remove(testFile);
+        await fileSystem.Remove(testFile,CancellationToken.None);
 
 
         Assert.That(File.Exists(testFile), Is.False);
         Assert.That(File.Exists(testFile), Is.False);
     }
     }
@@ -330,7 +330,7 @@ public class IOTests : IDisposable
 
 
         try
         try
         {
         {
-            using (var tempStream = fileSystem.OpenTempFileStream())
+            using (var tempStream = await fileSystem.OpenTempFileStream(CancellationToken.None))
             {
             {
                 await tempStream.WriteAsync(new("temp content"), CancellationToken.None);
                 await tempStream.WriteAsync(new("temp content"), CancellationToken.None);
 
 
@@ -363,7 +363,7 @@ public class IOTests : IDisposable
     {
     {
         var testFile = GetTestFilePath("buffer_test.txt");
         var testFile = GetTestFilePath("buffer_test.txt");
 
 
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.WriteText,CancellationToken.None))
         {
         {
             // Set no buffering
             // Set no buffering
             stream.SetVBuf(LuaFileBufferingMode.NoBuffering, 0);
             stream.SetVBuf(LuaFileBufferingMode.NoBuffering, 0);
@@ -395,12 +395,12 @@ public class IOTests : IDisposable
 
 
         // Test with char array
         // Test with char array
         var charArray = "Hello from char array".ToCharArray();
         var charArray = "Hello from char array".ToCharArray();
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.WriteText, CancellationToken.None))
         {
         {
             await stream.WriteAsync(new(charArray), CancellationToken.None);
             await stream.WriteAsync(new(charArray), CancellationToken.None);
         }
         }
 
 
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.ReadText,CancellationToken.None))
         {
         {
             var content = await stream.ReadAllAsync(CancellationToken.None);
             var content = await stream.ReadAllAsync(CancellationToken.None);
             Assert.That(content.ReadString(), Is.EqualTo("Hello from char array"));
             Assert.That(content.ReadString(), Is.EqualTo("Hello from char array"));
@@ -408,12 +408,12 @@ public class IOTests : IDisposable
 
 
         // Test with partial char array
         // Test with partial char array
         var longCharArray = "Hello World!!!".ToCharArray();
         var longCharArray = "Hello World!!!".ToCharArray();
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.WriteText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.WriteText,CancellationToken.None))
         {
         {
             await stream.WriteAsync(new(longCharArray.AsMemory(0, 11)), CancellationToken.None); // Only "Hello World"
             await stream.WriteAsync(new(longCharArray.AsMemory(0, 11)), CancellationToken.None); // Only "Hello World"
         }
         }
 
 
-        using (var stream = fileSystem.Open(testFile, LuaFileMode.ReadText))
+        using (var stream = await fileSystem.Open(testFile, LuaFileMode.ReadText,CancellationToken.None))
         {
         {
             var content = await stream.ReadAllAsync(CancellationToken.None);
             var content = await stream.ReadAllAsync(CancellationToken.None);
             Assert.That(content.ReadString(), Is.EqualTo("Hello World"));
             Assert.That(content.ReadString(), Is.EqualTo("Hello World"));