Browse Source

add: stdio and os abstractions

Akeit0 6 months ago
parent
commit
4e7b4c1588

+ 33 - 0
src/Lua/IO/ConsoleStandardIO.cs

@@ -0,0 +1,33 @@
+using Lua.Standard.Internal;
+
+namespace Lua.IO;
+
+/// <summary>
+/// Default implementation of ILuaStandardIO using Console
+/// </summary>
+public sealed class ConsoleStandardIO : ILuaStandardIO
+{
+    ILuaIOStream? standardInput;
+
+    public ILuaIOStream Input => standardInput ??=
+        new StandardIOStream(ILuaIOStream.CreateStreamWrapper(
+            ConsoleHelper.OpenStandardInput(),
+            LuaFileOpenMode.Read));
+
+    ILuaIOStream? standardOutput;
+
+    public ILuaIOStream Output
+
+        => standardOutput ??=
+            new StandardIOStream(ILuaIOStream.CreateStreamWrapper(
+                ConsoleHelper.OpenStandardOutput(),
+                LuaFileOpenMode.Write));
+
+
+    ILuaIOStream? standardError;
+
+    public ILuaIOStream Error => standardError ??=
+        new StandardIOStream(ILuaIOStream.CreateStreamWrapper(
+            ConsoleHelper.OpenStandardError(),
+            LuaFileOpenMode.Write));
+}

+ 103 - 0
src/Lua/IO/FileSystem.cs

@@ -0,0 +1,103 @@
+namespace Lua.IO
+{
+    public sealed class FileSystem : ILuaFileSystem
+    {
+        public static (FileMode, FileAccess access) GetFileMode(LuaFileOpenMode luaFileOpenMode)
+        {
+            return luaFileOpenMode switch
+            {
+                LuaFileOpenMode.Read => (FileMode.Open, FileAccess.Read),
+                LuaFileOpenMode.Write => (FileMode.Create, FileAccess.Write),
+                LuaFileOpenMode.Append => (FileMode.Append, FileAccess.Write),
+                LuaFileOpenMode.ReadWriteOpen => (FileMode.Open, FileAccess.ReadWrite),
+                LuaFileOpenMode.ReadWriteCreate => (FileMode.Truncate, FileAccess.ReadWrite),
+                LuaFileOpenMode.ReadAppend => (FileMode.Append, FileAccess.ReadWrite),
+                _ => throw new ArgumentOutOfRangeException(nameof(luaFileOpenMode), luaFileOpenMode, null)
+            };
+        }
+
+        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;
+            }
+        }
+
+
+        ILuaIOStream Open(string path, LuaFileOpenMode luaMode, LuaFileContentType contentType)
+        {
+            var (mode, access) = GetFileMode(luaMode);
+            Stream stream;
+
+            if (luaMode == LuaFileOpenMode.ReadAppend)
+            {
+                stream = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete);
+            }
+            else
+            {
+                stream = File.Open(path, mode, access, FileShare.ReadWrite | FileShare.Delete);
+            }
+
+            ILuaIOStream wrapper = contentType == LuaFileContentType.Binary
+                ? new BinaryLuaIOStream(luaMode, stream)
+                : new TextLuaIOStream(luaMode, stream);
+
+            if (luaMode == LuaFileOpenMode.ReadAppend)
+            {
+                wrapper.Seek(0, SeekOrigin.End);
+            }
+
+            return wrapper;
+        }
+
+        public ILuaIOStream Open(string path, LuaFileMode mode)
+        {
+            if (mode is LuaFileMode.ReadBinaryOrText)
+            {
+                return new LuaChunkStream(File.OpenRead(path));
+            }
+
+            var openMode = mode.GetOpenMode();
+            var contentType = mode.GetContentType();
+            return Open(path, openMode, contentType);
+        }
+
+        public ILuaIOStream Open(string path, string mode)
+        {
+            var flags = LuaFileModeExtensions.ParseModeString(mode);
+            return Open(path, flags);
+        }
+
+        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);
+        }
+
+        static readonly string directorySeparator = Path.DirectorySeparatorChar.ToString();
+        public string DirectorySeparator => directorySeparator;
+
+        public string GetTempFileName()
+        {
+            return Path.GetTempFileName();
+        }
+
+        public ILuaIOStream OpenTempFileStream()
+        {
+            return new TextLuaIOStream(LuaFileOpenMode.ReadAppend, File.Open(Path.GetTempFileName(), FileMode.Open, FileAccess.ReadWrite));
+        }
+    }
+}

+ 1 - 133
src/Lua/IO/ILuaFileSystem.cs

@@ -1,7 +1,4 @@
-using Lua.Internal;
-using System.Text;
-
-namespace Lua.IO;
+namespace Lua.IO;
 
 
 public interface ILuaFileSystem
 public interface ILuaFileSystem
 {
 {
@@ -12,133 +9,4 @@ public interface ILuaFileSystem
     public string DirectorySeparator { get; }
     public string DirectorySeparator { get; }
     public string GetTempFileName();
     public string GetTempFileName();
     public ILuaIOStream OpenTempFileStream();
     public ILuaIOStream OpenTempFileStream();
-}
-
-public interface ILuaIOStream : IDisposable
-{
-    public LuaFileOpenMode Mode { get; }
-
-    public LuaFileContentType ContentType { get; }
-    public ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken);
-    public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken);
-    public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken);
-    public ValueTask WriteAsync(LuaFileContent content, CancellationToken cancellationToken);
-    public ValueTask FlushAsync(CancellationToken cancellationToken);
-    public void SetVBuf(LuaFileBufferingMode mode, int size);
-    public long Seek(long offset, SeekOrigin origin);
-
-    public static ILuaIOStream CreateStreamWrapper(Stream stream, LuaFileOpenMode mode, LuaFileContentType contentType = LuaFileContentType.Text)
-    {
-        return contentType == LuaFileContentType.Binary
-            ? new BinaryLuaIOStream(mode, stream)
-            : new TextLuaIOStream(mode, stream);
-    }
-
-    public void Close()
-    {
-        Dispose();
-    }
-}
-
-public sealed class FileSystem : ILuaFileSystem
-{
-    public static readonly FileSystem Instance = new();
-
-    public static (FileMode, FileAccess access) GetFileMode(LuaFileOpenMode luaFileOpenMode)
-    {
-        return luaFileOpenMode switch
-        {
-            LuaFileOpenMode.Read => (FileMode.Open, FileAccess.Read),
-            LuaFileOpenMode.Write => (FileMode.Create, FileAccess.Write),
-            LuaFileOpenMode.Append => (FileMode.Append, FileAccess.Write),
-            LuaFileOpenMode.ReadWriteOpen => (FileMode.Open, FileAccess.ReadWrite),
-            LuaFileOpenMode.ReadWriteCreate => (FileMode.Truncate, FileAccess.ReadWrite),
-            LuaFileOpenMode.ReadAppend => (FileMode.Append, FileAccess.ReadWrite),
-            _ => throw new ArgumentOutOfRangeException(nameof(luaFileOpenMode), luaFileOpenMode, null)
-        };
-    }
-
-    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;
-        }
-    }
-
-
-    ILuaIOStream Open(string path, LuaFileOpenMode luaMode, LuaFileContentType contentType)
-    {
-        var (mode, access) = GetFileMode(luaMode);
-        Stream stream;
-
-        if (luaMode == LuaFileOpenMode.ReadAppend)
-        {
-            stream = File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete);
-        }
-        else
-        {
-            stream = File.Open(path, mode, access, FileShare.ReadWrite | FileShare.Delete);
-        }
-
-        ILuaIOStream wrapper = contentType == LuaFileContentType.Binary
-            ? new BinaryLuaIOStream(luaMode, stream)
-            : new TextLuaIOStream(luaMode, stream);
-
-        if (luaMode == LuaFileOpenMode.ReadAppend)
-        {
-            wrapper.Seek(0, SeekOrigin.End);
-        }
-
-        return wrapper;
-    }
-
-    public ILuaIOStream Open(string path, LuaFileMode mode)
-    {
-        if (mode is LuaFileMode.ReadBinaryOrText)
-        {
-            return new LuaChunkStream(File.OpenRead(path));
-        }
-
-        var openMode = mode.GetOpenMode();
-        var contentType = mode.GetContentType();
-        return Open(path, openMode, contentType);
-    }
-
-    public ILuaIOStream Open(string path, string mode)
-    {
-        var flags = LuaFileModeExtensions.ParseModeString(mode);
-        return Open(path, flags);
-    }
-
-    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);
-    }
-
-    static readonly string directorySeparator = Path.DirectorySeparatorChar.ToString();
-    public string DirectorySeparator => directorySeparator;
-
-    public string GetTempFileName()
-    {
-        return Path.GetTempFileName();
-    }
-
-    public ILuaIOStream OpenTempFileStream()
-    {
-        return new TextLuaIOStream(LuaFileOpenMode.ReadAppend, File.Open(Path.GetTempFileName(), FileMode.Open, FileAccess.ReadWrite));
-    }
 }
 }

+ 28 - 0
src/Lua/IO/ILuaIOStream.cs

@@ -0,0 +1,28 @@
+namespace Lua.IO
+{
+    public interface ILuaIOStream : IDisposable
+    {
+        public LuaFileOpenMode Mode { get; }
+
+        public LuaFileContentType ContentType { get; }
+        public ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken);
+        public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken);
+        public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken);
+        public ValueTask WriteAsync(LuaFileContent content, CancellationToken cancellationToken);
+        public ValueTask FlushAsync(CancellationToken cancellationToken);
+        public void SetVBuf(LuaFileBufferingMode mode, int size);
+        public long Seek(long offset, SeekOrigin origin);
+
+        public static ILuaIOStream CreateStreamWrapper(Stream stream, LuaFileOpenMode mode, LuaFileContentType contentType = LuaFileContentType.Text)
+        {
+            return contentType == LuaFileContentType.Binary
+                ? new BinaryLuaIOStream(mode, stream)
+                : new TextLuaIOStream(mode, stream);
+        }
+
+        public void Close()
+        {
+            Dispose();
+        }
+    }
+}

+ 22 - 0
src/Lua/IO/ILuaStandardIO.cs

@@ -0,0 +1,22 @@
+namespace Lua.IO;
+
+/// <summary>
+/// Interface for standard IO operations (stdin, stdout, stderr)
+/// </summary>
+public interface ILuaStandardIO
+{
+    /// <summary>
+    /// Open standard input stream
+    /// </summary>
+    ILuaIOStream Input { get; }
+
+    /// <summary>
+    /// Open standard output stream
+    /// </summary>
+    ILuaIOStream Output { get; }
+
+    /// <summary>
+    /// Open standard error stream
+    /// </summary>
+    ILuaIOStream Error { get; }
+}

+ 37 - 0
src/Lua/IO/StandardIOStream.cs

@@ -0,0 +1,37 @@
+namespace Lua.IO
+{
+    /// <summary>
+    /// Wrapper for standard IO streams that prevents closing
+    /// </summary>
+    internal sealed class StandardIOStream(ILuaIOStream innerStream) : ILuaIOStream
+    {
+        public LuaFileOpenMode Mode => innerStream.Mode;
+        public LuaFileContentType ContentType => innerStream.ContentType;
+
+        public ValueTask<LuaFileContent> ReadAllAsync(CancellationToken cancellationToken)
+            => innerStream.ReadAllAsync(cancellationToken);
+
+        public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
+            => innerStream.ReadLineAsync(cancellationToken);
+
+        public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken)
+            => innerStream.ReadStringAsync(count, cancellationToken);
+
+        public ValueTask WriteAsync(LuaFileContent content, CancellationToken cancellationToken)
+            => innerStream.WriteAsync(content, cancellationToken);
+
+        public ValueTask FlushAsync(CancellationToken cancellationToken)
+            => innerStream.FlushAsync(cancellationToken);
+
+        public void SetVBuf(LuaFileBufferingMode mode, int size)
+            => innerStream.SetVBuf(mode, size);
+
+        public long Seek(long offset, SeekOrigin origin)
+            => innerStream.Seek(offset, origin);
+
+        public void Dispose()
+        {
+            throw new IOException("cannot close standard file");
+        }
+    }
+}

+ 1 - 1
src/Lua/Lua.csproj

@@ -6,7 +6,7 @@
         <ImplicitUsings>enable</ImplicitUsings>
         <ImplicitUsings>enable</ImplicitUsings>
         <Nullable>enable</Nullable>
         <Nullable>enable</Nullable>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
         <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
-
+        <NoWarn>CS0436</NoWarn>
         <!-- NuGet Packaging -->
         <!-- NuGet Packaging -->
         <PackageId>LuaCSharp</PackageId>
         <PackageId>LuaCSharp</PackageId>
         <PackageTags>lua;interpreter</PackageTags>
         <PackageTags>lua;interpreter</PackageTags>

+ 17 - 3
src/Lua/LuaState.cs

@@ -4,6 +4,7 @@ using System.Runtime.CompilerServices;
 using Lua.Internal;
 using Lua.Internal;
 using Lua.IO;
 using Lua.IO;
 using Lua.Loaders;
 using Lua.Loaders;
+using Lua.Platforms;
 using Lua.Runtime;
 using Lua.Runtime;
 using Lua.Standard;
 using Lua.Standard;
 using System.Buffers;
 using System.Buffers;
@@ -36,10 +37,16 @@ public sealed class LuaState
     public LuaMainThread MainThread => mainThread;
     public LuaMainThread MainThread => mainThread;
 
 
     public LuaThreadAccess TopLevelAccess => new(mainThread, 0);
     public LuaThreadAccess TopLevelAccess => new(mainThread, 0);
+    
+    public  ILuaPlatform Platform { get; }
 
 
     public ILuaModuleLoader ModuleLoader { get; set; } = FileModuleLoader.Instance;
     public ILuaModuleLoader ModuleLoader { get; set; } = FileModuleLoader.Instance;
 
 
-    public ILuaFileSystem FileSystem { get; set; } = Lua.IO.FileSystem.Instance;
+    public ILuaFileSystem FileSystem => Platform.FileSystem ?? throw new InvalidOperationException("FileSystem is not set. Please set it before using LuaState.");
+
+    public ILuaOperatingSystem OperatingSystem   => Platform.OperatingSystem ?? throw new InvalidOperationException("OperatingSystem is not set. Please set it before using LuaState.");
+
+    public ILuaStandardIO StandardIO => Platform.StandardIO;
 
 
     // metatables
     // metatables
     LuaTable? nilMetatable;
     LuaTable? nilMetatable;
@@ -51,16 +58,23 @@ public sealed class LuaState
 
 
     public static LuaState Create()
     public static LuaState Create()
     {
     {
-        return new();
+        return Create(LuaPlatform.Default);
+    }
+    
+    public static LuaState Create(ILuaPlatform platform)
+    {
+        var state = new LuaState(platform);
+        return state;
     }
     }
 
 
-    LuaState()
+    LuaState(ILuaPlatform platform)
     {
     {
         mainThread = new(this);
         mainThread = new(this);
         environment = new();
         environment = new();
         envUpValue = UpValue.Closed(environment);
         envUpValue = UpValue.Closed(environment);
         registry[ModuleLibrary.LoadedKeyForRegistry] = new LuaTable(0, 8);
         registry[ModuleLibrary.LoadedKeyForRegistry] = new LuaTable(0, 8);
         registry[ModuleLibrary.PreloadKeyForRegistry] = new LuaTable(0, 8);
         registry[ModuleLibrary.PreloadKeyForRegistry] = new LuaTable(0, 8);
+        Platform = platform;
     }
     }
 
 
 
 

+ 32 - 0
src/Lua/Platforms/ILuaOperatingSystem.cs

@@ -0,0 +1,32 @@
+namespace Lua.Platforms;
+
+/// <summary>
+/// Interface for operating system operations beyond file system
+/// </summary>
+public interface ILuaOperatingSystem
+{
+    /// <summary>
+    /// Get environment variable value
+    /// </summary>
+    string? GetEnvironmentVariable(string name);
+
+    /// <summary>
+    /// Exit the application with specified code
+    /// </summary>
+    void Exit(int exitCode);
+
+    /// <summary>
+    /// Get current process start time for clock calculations
+    /// </summary>
+    DateTime GetProcessStartTime();
+
+    /// <summary>
+    /// Get current UTC time
+    /// </summary>
+    DateTime GetCurrentUtcTime();
+
+    /// <summary>
+    /// Get local time zone offset from UTC
+    /// </summary>
+    TimeSpan GetLocalTimeZoneOffset();
+}

+ 35 - 0
src/Lua/Platforms/OperatingSystem.cs

@@ -0,0 +1,35 @@
+using System.Diagnostics;
+
+namespace Lua.Platforms
+{
+    /// <summary>
+    /// Default implementation of ILuaOperatingSystem
+    /// </summary>
+    public sealed class OperatingSystem : ILuaOperatingSystem
+    {
+        public string? GetEnvironmentVariable(string name)
+        {
+            return Environment.GetEnvironmentVariable(name);
+        }
+
+        public void Exit(int exitCode)
+        {
+            Environment.Exit(exitCode);
+        }
+
+        public DateTime GetProcessStartTime()
+        {
+            return Process.GetCurrentProcess().StartTime;
+        }
+
+        public DateTime GetCurrentUtcTime()
+        {
+            return DateTime.UtcNow;
+        }
+
+        public TimeSpan GetLocalTimeZoneOffset()
+        {
+            return TimeZoneInfo.Local.BaseUtcOffset;
+        }
+    }
+}

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

@@ -290,14 +290,20 @@ public sealed class BasicLibrary
 
 
     public async ValueTask<int> Print(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     public async ValueTask<int> Print(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
+        var stdout = context.State.StandardIO.Output;
+        
         for (int i = 0; i < context.ArgumentCount; i++)
         for (int i = 0; i < context.ArgumentCount; i++)
         {
         {
             await context.Arguments[i].CallToStringAsync(context, cancellationToken);
             await context.Arguments[i].CallToStringAsync(context, cancellationToken);
-            Console.Write(context.Thread.Stack.Pop().Read<string>());
-            Console.Write('\t');
+            await stdout.WriteAsync(new (context.Thread.Stack.Pop().Read<string>()), cancellationToken);
+            if (i < context.ArgumentCount - 1)
+            {
+                await stdout.WriteAsync( new("\t"), cancellationToken);
+            }
         }
         }
 
 
-        Console.WriteLine();
+        await stdout.WriteAsync(new("\n"), cancellationToken);
+        await stdout.FlushAsync(cancellationToken);
         return context.Return();
         return context.Return();
     }
     }
 
 

+ 16 - 1
src/Lua/Standard/FileHandle.cs

@@ -46,7 +46,7 @@ public class FileHandle : ILuaUserData
         fileHandleMetatable[Metamethods.Index] = IndexMetamethod;
         fileHandleMetatable[Metamethods.Index] = IndexMetamethod;
     }
     }
 
 
-    public FileHandle(Stream stream,LuaFileOpenMode mode, LuaFileContentType type =LuaFileContentType.Text) : this(ILuaIOStream.CreateStreamWrapper( stream,mode,type)) { }
+    public FileHandle(Stream stream, LuaFileOpenMode mode, LuaFileContentType type = LuaFileContentType.Text) : this(ILuaIOStream.CreateStreamWrapper(stream, mode, type)) { }
 
 
     public FileHandle(ILuaIOStream stream)
     public FileHandle(ILuaIOStream stream)
     {
     {
@@ -73,6 +73,21 @@ public class FileHandle : ILuaUserData
         return stream.WriteAsync(content, cancellationToken);
         return stream.WriteAsync(content, cancellationToken);
     }
     }
 
 
+    public ValueTask WriteAsync(string content, CancellationToken cancellationToken)
+    {
+        return stream.WriteAsync(new(content), cancellationToken);
+    }
+
+    public ValueTask WriteAsync(ReadOnlyMemory<char> content, CancellationToken cancellationToken)
+    {
+        return stream.WriteAsync(new (content), cancellationToken);
+    }
+
+    public ValueTask WriteAsync(IBinaryData content, CancellationToken cancellationToken)
+    {
+        return stream.WriteAsync(new(content), cancellationToken);
+    }
+
     public long Seek(string whence, long offset) =>
     public long Seek(string whence, long offset) =>
         whence switch
         whence switch
         {
         {

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

@@ -42,18 +42,18 @@ internal static class IOHelper
                 var arg = context.Arguments[i];
                 var arg = context.Arguments[i];
                 if (arg.TryRead<string>(out var str))
                 if (arg.TryRead<string>(out var str))
                 {
                 {
-                    await file.WriteAsync(new(str), cancellationToken);
+                    await file.WriteAsync(str, cancellationToken);
                 }
                 }
                 else if (arg.TryRead<double>(out var d))
                 else if (arg.TryRead<double>(out var d))
                 {
                 {
                     using var fileBuffer = new PooledArray<char>(64);
                     using var fileBuffer = new PooledArray<char>(64);
                     var span = fileBuffer.AsSpan();
                     var span = fileBuffer.AsSpan();
                     d.TryFormat(span, out var charsWritten);
                     d.TryFormat(span, out var charsWritten);
-                    await file.WriteAsync(new(fileBuffer.UnderlyingArray.AsMemory(0,charsWritten) ), cancellationToken);
+                    await file.WriteAsync(fileBuffer.UnderlyingArray.AsMemory(0,charsWritten) , cancellationToken);
                 }
                 }
                 else if (arg.TryRead<IBinaryData>(out var binaryData))
                 else if (arg.TryRead<IBinaryData>(out var binaryData))
                 {
                 {
-                    await file.WriteAsync(new (binaryData), cancellationToken);
+                    await file.WriteAsync( binaryData, cancellationToken);
                 }
                 }
                 else
                 else
                 {
                 {

+ 4 - 4
src/Lua/Standard/OpenLibsExtensions.cs

@@ -1,6 +1,5 @@
 using Lua.IO;
 using Lua.IO;
 using Lua.Runtime;
 using Lua.Runtime;
-using Lua.Standard.Internal;
 
 
 namespace Lua.Standard;
 namespace Lua.Standard;
 
 
@@ -48,9 +47,10 @@ public static class OpenLibsExtensions
         }
         }
 
 
         var registry = state.Registry;
         var registry = state.Registry;
-        var stdin = new LuaValue(new FileHandle(ConsoleHelper.OpenStandardInput(),LuaFileOpenMode.Read));
-        var stdout = new LuaValue(new FileHandle(ConsoleHelper.OpenStandardOutput(),LuaFileOpenMode.Write));
-        var stderr = new LuaValue(new FileHandle( ConsoleHelper.OpenStandardError(),LuaFileOpenMode.Write));
+        var standardIO = state.StandardIO;
+        var stdin = new LuaValue(new FileHandle(standardIO.Input));
+        var stdout = new LuaValue(new FileHandle(standardIO.Output));
+        var stderr = new LuaValue(new FileHandle(standardIO.Error));
         registry["_IO_input"] = stdin;
         registry["_IO_input"] = stdin;
         registry["_IO_output"] = stdout;
         registry["_IO_output"] = stdout;
         io["stdin"] = stdin;
         io["stdin"] = stdin;

+ 34 - 32
src/Lua/Standard/OperatingSystemLibrary.cs

@@ -1,4 +1,3 @@
-using System.Diagnostics;
 using Lua.Standard.Internal;
 using Lua.Standard.Internal;
 
 
 namespace Lua.Standard;
 namespace Lua.Standard;
@@ -11,17 +10,17 @@ public sealed class OperatingSystemLibrary
     {
     {
         Functions =
         Functions =
         [
         [
-            new("os","clock", Clock),
-            new("os","date", Date),
-            new("os","difftime", DiffTime),
-            new("os","execute", Execute),
-            new("os","exit", Exit),
-            new("os","getenv", GetEnv),
-            new("os","remove", Remove),
-            new("os","rename", Rename),
-            new("os","setlocale", SetLocale),
-            new("os","time", Time),
-            new("os","tmpname", TmpName),
+            new("os", "clock", Clock),
+            new("os", "date", Date),
+            new("os", "difftime", DiffTime),
+            new("os", "execute", Execute),
+            new("os", "exit", Exit),
+            new("os", "getenv", GetEnv),
+            new("os", "remove", Remove),
+            new("os", "rename", Rename),
+            new("os", "setlocale", SetLocale),
+            new("os", "time", Time),
+            new("os", "tmpname", TmpName),
         ];
         ];
     }
     }
 
 
@@ -29,7 +28,8 @@ public sealed class OperatingSystemLibrary
 
 
     public ValueTask<int> Clock(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     public ValueTask<int> Clock(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
-        return new(context.Return(DateTimeHelper.GetUnixTime(DateTime.UtcNow, Process.GetCurrentProcess().StartTime)));
+        var os = context.State.OperatingSystem;
+        return new(context.Return(DateTimeHelper.GetUnixTime(os.GetCurrentUtcTime(), os.GetProcessStartTime())));
     }
     }
 
 
     public ValueTask<int> Date(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     public ValueTask<int> Date(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
@@ -46,7 +46,7 @@ public sealed class OperatingSystemLibrary
         }
         }
         else
         else
         {
         {
-            now = DateTime.UtcNow;
+            now = context.State.OperatingSystem.GetCurrentUtcTime();
         }
         }
 
 
         var isDst = false;
         var isDst = false;
@@ -56,23 +56,25 @@ public sealed class OperatingSystemLibrary
         }
         }
         else
         else
         {
         {
-            now = TimeZoneInfo.ConvertTimeFromUtc(now, TimeZoneInfo.Local);
+            var offset = context.State.OperatingSystem.GetLocalTimeZoneOffset();
+            now =   now.Add(offset);
             isDst = now.IsDaylightSavingTime();
             isDst = now.IsDaylightSavingTime();
         }
         }
 
 
-        if (format == "*t")
+        if (format is "*t")
         {
         {
-            var table = new LuaTable();
-
-            table["year"] = now.Year;
-            table["month"] = now.Month;
-            table["day"] = now.Day;
-            table["hour"] = now.Hour;
-            table["min"] = now.Minute;
-            table["sec"] = now.Second;
-            table["wday"] = ((int)now.DayOfWeek) + 1;
-            table["yday"] = now.DayOfYear;
-            table["isdst"] = isDst;
+            var table = new LuaTable
+            {
+                ["year"] = now.Year,
+                ["month"] = now.Month,
+                ["day"] = now.Day,
+                ["hour"] = now.Hour,
+                ["min"] = now.Minute,
+                ["sec"] = now.Second,
+                ["wday"] = ((int)now.DayOfWeek) + 1,
+                ["yday"] = now.DayOfYear,
+                ["isdst"] = isDst
+            };
 
 
             return new(context.Return(table));
             return new(context.Return(table));
         }
         }
@@ -113,11 +115,11 @@ public sealed class OperatingSystemLibrary
 
 
             if (code.TryRead<bool>(out var b))
             if (code.TryRead<bool>(out var b))
             {
             {
-                Environment.Exit(b ? 0 : 1);
+                context.State.OperatingSystem.Exit(b ? 0 : 1);
             }
             }
             else if (code.TryRead<int>(out var d))
             else if (code.TryRead<int>(out var d))
             {
             {
-                Environment.Exit(d);
+                context.State.OperatingSystem.Exit(d);
             }
             }
             else
             else
             {
             {
@@ -126,7 +128,7 @@ public sealed class OperatingSystemLibrary
         }
         }
         else
         else
         {
         {
-            Environment.Exit(0);
+            context.State.OperatingSystem.Exit(0);
         }
         }
 
 
         return new(context.Return());
         return new(context.Return());
@@ -135,7 +137,7 @@ public sealed class OperatingSystemLibrary
     public ValueTask<int> GetEnv(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     public ValueTask<int> GetEnv(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     {
     {
         var variable = context.GetArgument<string>(0);
         var variable = context.GetArgument<string>(0);
-        return new(context.Return(Environment.GetEnvironmentVariable(variable) ?? LuaValue.Nil));
+        return new(context.Return(context.State.OperatingSystem.GetEnvironmentVariable(variable) ?? LuaValue.Nil));
     }
     }
 
 
     public ValueTask<int> Remove(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
     public ValueTask<int> Remove(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
@@ -184,7 +186,7 @@ public sealed class OperatingSystemLibrary
         }
         }
         else
         else
         {
         {
-            return new(context.Return(DateTimeHelper.GetUnixTime(DateTime.UtcNow)));
+            return new(context.Return(DateTimeHelper.GetUnixTime(context.State.OperatingSystem.GetCurrentUtcTime())));
         }
         }
     }
     }
 
 

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

@@ -1,4 +1,5 @@
 using Lua.IO;
 using Lua.IO;
+using Lua.Platforms;
 using Lua.Standard;
 using Lua.Standard;
 using Lua.Tests.Helpers;
 using Lua.Tests.Helpers;
 
 
@@ -26,8 +27,7 @@ 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();
-        state.FileSystem = fileSystem;
+        var state = LuaState.Create(LuaPlatform.Default with{FileSystem = fileSystem});
         state.OpenStandardLibraries();
         state.OpenStandardLibraries();
         try
         try
         {
         {
@@ -56,8 +56,7 @@ 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();
-        state.FileSystem = fileSystem;
+        var state = LuaState.Create(LuaPlatform.Default with{FileSystem = fileSystem});
         state.OpenStandardLibraries();
         state.OpenStandardLibraries();
 
 
         await state.DoStringAsync(
         await state.DoStringAsync(

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

@@ -127,7 +127,7 @@ public class IOTests : IDisposable
     }
     }
 
 
     [Test]
     [Test]
-    public async Task TextStream_Cannot_Write_Binary_Content()
+    public  void TextStream_Cannot_Write_Binary_Content()
     {
     {
         var testFile = GetTestFilePath("text_binary_mix.txt");
         var testFile = GetTestFilePath("text_binary_mix.txt");
 
 
@@ -140,7 +140,7 @@ public class IOTests : IDisposable
     }
     }
 
 
     [Test]
     [Test]
-    public async Task BinaryStream_Cannot_Write_Text_Content()
+    public void BinaryStream_Cannot_Write_Text_Content()
     {
     {
         var testFile = GetTestFilePath("binary_text_mix.bin");
         var testFile = GetTestFilePath("binary_text_mix.bin");