Browse Source

Add: open, close, file:write, file:flush

AnnulusGames 1 year ago
parent
commit
5ec2cbdbdd

+ 26 - 0
src/Lua/Standard/IO/FileCloseFunction.cs

@@ -0,0 +1,26 @@
+namespace Lua.Standard.IO;
+
+public sealed class FileCloseFunction : LuaFunction
+{
+    public override string Name => "close";
+    public static readonly FileCloseFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var file = context.ReadArgument<FileHandle>(0);
+
+        try
+        {
+            file.Stream.Close();
+            buffer.Span[0] = true;
+            return new(1);
+        }
+        catch (IOException ex)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            buffer.Span[1] = ex.Message;
+            buffer.Span[2] = ex.HResult;
+            return new(3);
+        }
+    }
+}

+ 26 - 0
src/Lua/Standard/IO/FileFlushFunction.cs

@@ -0,0 +1,26 @@
+namespace Lua.Standard.IO;
+
+public sealed class FileFlushFunction : LuaFunction
+{
+    public override string Name => "flush";
+    public static readonly FileFlushFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var file = context.ReadArgument<FileHandle>(0);
+
+        try
+        {
+            file.Stream.Flush();
+            buffer.Span[0] = true;
+            return new(1);
+        }
+        catch (IOException ex)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            buffer.Span[1] = ex.Message;
+            buffer.Span[2] = ex.HResult;
+            return new(3);
+        }
+    }
+}

+ 44 - 2
src/Lua/Standard/IO/FileHandle.cs

@@ -1,6 +1,48 @@
+using Lua.Runtime;
+
 namespace Lua.Standard.IO;
 
-public class FileHandle(FileStream stream) : LuaUserData
+public class FileHandle : LuaUserData
 {
-    public FileStream Stream { get; } = stream;
+    class IndexMetamethod : LuaFunction
+    {
+        protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+        {
+            context.ReadArgument<FileHandle>(0);
+            var key = context.ReadArgument(1);
+
+            if (key.TryRead<string>(out var name))
+            {
+                buffer.Span[0] = name switch
+                {
+                    "write" => FileWriteFunction.Instance,
+                    "flush" => FileFlushFunction.Instance,
+                    "close" => FileCloseFunction.Instance,
+                    _ => LuaValue.Nil,
+                };
+            }
+            else
+            {
+                buffer.Span[0] = LuaValue.Nil;
+            }
+
+            return new(1);
+        }
+    }
+
+    public FileStream Stream { get; }
+
+    static readonly LuaTable fileHandleMetatable;
+
+    static FileHandle()
+    {
+        fileHandleMetatable = new LuaTable();
+        fileHandleMetatable[Metamethods.Index] = new IndexMetamethod();
+    }
+
+    public FileHandle(FileStream stream)
+    {
+        Stream = stream;
+        Metatable = fileHandleMetatable;
+    }
 }

+ 2 - 2
src/Lua/Standard/IO/OpenFunction.cs → src/Lua/Standard/IO/FileOpenFunction.cs

@@ -1,9 +1,9 @@
 namespace Lua.Standard.IO;
 
-public sealed class OpenFunction : LuaFunction
+public sealed class FileOpenFunction : LuaFunction
 {
     public override string Name => "open";
-    public static readonly OpenFunction Instance = new();
+    public static readonly FileOpenFunction Instance = new();
 
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {

+ 54 - 0
src/Lua/Standard/IO/FileWriteFunction.cs

@@ -0,0 +1,54 @@
+using System.Buffers.Text;
+using System.Text;
+using Lua.Internal;
+
+namespace Lua.Standard.IO;
+
+// TODO: optimize (use IBuffertWrite<byte>)
+
+public sealed class FileWriteFunction : LuaFunction
+{
+    public override string Name => "write";
+    public static readonly FileWriteFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var file = context.ReadArgument<FileHandle>(0);
+        try
+        {
+            for (int i = 1; i < context.ArgumentCount; i++)
+            {
+                var arg = context.Arguments[i];
+                if (arg.TryRead<string>(out var str))
+                {
+                    using var fileBuffer = new PooledArray<byte>(str.Length * 3);
+                    var bytesWritten = Encoding.UTF8.GetBytes(str, fileBuffer.AsSpan());
+                    file.Stream.Write(fileBuffer.AsSpan()[..bytesWritten]);
+                }
+                else if (arg.TryRead<double>(out var d))
+                {
+                    using var fileBuffer = new PooledArray<byte>(64);
+                    if (!Utf8Formatter.TryFormat(d, fileBuffer.AsSpan(), out var bytesWritten))
+                    {
+                        throw new ArgumentException("Destination is too short.");
+                    }
+                    file.Stream.Write(fileBuffer.AsSpan()[..bytesWritten]);
+                }
+                else
+                {
+                    LuaRuntimeException.BadArgument(context.State.GetTraceback(), i + 1, Name);
+                }
+            }
+        }
+        catch (IOException ex)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            buffer.Span[1] = ex.Message;
+            buffer.Span[2] = ex.HResult;
+            return new(3);
+        }
+
+        buffer.Span[0] = file;
+        return new(1);
+    }
+}

+ 18 - 1
src/Lua/Standard/OpenLibExtensions.cs

@@ -1,5 +1,6 @@
 using Lua.Standard.Basic;
 using Lua.Standard.Coroutines;
+using Lua.Standard.IO;
 using Lua.Standard.Mathematics;
 using Lua.Standard.Modules;
 using Lua.Standard.Table;
@@ -72,6 +73,11 @@ public static class OpenLibExtensions
         SortFunction.Instance,
     ];
 
+    static readonly LuaFunction[] ioFunctions = [
+        FileOpenFunction.Instance,
+        FileCloseFunction.Instance,
+    ];
+
     public static void OpenBasicLibrary(this LuaState state)
     {
         // basic
@@ -114,7 +120,7 @@ public static class OpenLibExtensions
         var package = new LuaTable(0, 1);
         package["loaded"] = new LuaTable();
         state.Environment["package"] = package;
-        
+
         state.Environment[RequireFunction.Instance.Name] = RequireFunction.Instance;
     }
 
@@ -128,4 +134,15 @@ public static class OpenLibExtensions
 
         state.Environment["table"] = table;
     }
+
+    public static void OpenIOLibrary(this LuaState state)
+    {
+        var io = new LuaTable(0, ioFunctions.Length);
+        foreach (var func in ioFunctions)
+        {
+            io[func.Name] = func;
+        }
+
+        state.Environment["io"] = io;
+    }
 }