Browse Source

Add: write/read

AnnulusGames 1 year ago
parent
commit
7477ea6795

+ 1 - 0
src/Lua/Standard/IO/FileHandle.cs

@@ -16,6 +16,7 @@ public class FileHandle : LuaUserData
                 buffer.Span[0] = name switch
                 {
                     "write" => FileWriteFunction.Instance,
+                    "read" => FileReadFunction.Instance,
                     "flush" => FileFlushFunction.Instance,
                     "close" => CloseFunction.Instance,
                     _ => LuaValue.Nil,

+ 18 - 0
src/Lua/Standard/IO/FileReadFunction.cs

@@ -0,0 +1,18 @@
+using System.Buffers.Text;
+using System.Text;
+using Lua.Internal;
+
+namespace Lua.Standard.IO;
+
+public sealed class FileReadFunction : LuaFunction
+{
+    public override string Name => "read";
+    public static readonly FileReadFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var file = context.ReadArgument<FileHandle>(0);
+        var resultCount = IOHelper.Read(file, Name, context, context.Arguments[1..], buffer, cancellationToken);
+        return new(resultCount);
+    }
+}

+ 2 - 42
src/Lua/Standard/IO/FileWriteFunction.cs

@@ -1,11 +1,5 @@
-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";
@@ -14,41 +8,7 @@ public sealed class FileWriteFunction : LuaFunction
     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);
+        var resultCount = IOHelper.Write(file, Name, context, buffer, cancellationToken);
+        return new(resultCount);
     }
 }

+ 119 - 0
src/Lua/Standard/IO/IOHelper.cs

@@ -0,0 +1,119 @@
+using System.Buffers.Text;
+using System.Text;
+using Lua.Internal;
+
+namespace Lua.Standard.IO;
+
+internal static class IOHelper
+{
+    // TODO: optimize (use IBuffertWrite<byte>, async)
+
+    public static int Write(FileHandle file, string name, LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        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 3;
+        }
+
+        buffer.Span[0] = file;
+        return 1;
+    }
+
+    static readonly LuaValue[] defaultReadFormat = ["*l"];
+
+    public static int Read(FileHandle file, string name, LuaFunctionExecutionContext context, ReadOnlySpan<LuaValue> formats, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        if (formats.Length == 0)
+        {
+            formats = defaultReadFormat;
+        }
+
+        try
+        {
+            using var reader = new StreamReader(file.Stream);
+
+            for (int i = 0; i < formats.Length; i++)
+            {
+                var format = formats[i];
+                if (format.TryRead<string>(out var str))
+                {
+                    switch (str)
+                    {
+                        case "*n":
+                        case "*number":
+                            // TODO: support number format
+                            throw new NotImplementedException();
+                        case "*a":
+                        case "*all":
+                            buffer.Span[i] = reader.ReadToEnd();
+                            break;
+                        case "*l":
+                        case "*line":
+                            buffer.Span[i] = reader.ReadLine();
+                            break;
+                        case "L":
+                        case "*L":
+                            buffer.Span[i] = reader.ReadLine() + Environment.NewLine;
+                            break;
+                    }
+                }
+                else if (format.TryRead<double>(out var d))
+                {
+                    if (!MathEx.IsInteger(d))
+                    {
+                        throw new LuaRuntimeException(context.State.GetTraceback(), $"bad argument #{i + 1} to 'read' (number has no integer representation)");
+                    }
+                    
+                    // TODO:
+
+                }
+                else if (i == 0 && format.TryRead<FileHandle>(out var f) && f == file)
+                {
+                    continue;
+                }
+                else
+                {
+                    LuaRuntimeException.BadArgument(context.State.GetTraceback(), i + 1, name);
+                }
+            }
+
+            return formats.Length;
+        }
+        catch (IOException ex)
+        {
+            buffer.Span[0] = LuaValue.Nil;
+            buffer.Span[1] = ex.Message;
+            buffer.Span[2] = ex.HResult;
+            return 3;
+        }
+    }
+}

+ 14 - 0
src/Lua/Standard/IO/ReadFunction.cs

@@ -0,0 +1,14 @@
+namespace Lua.Standard.IO;
+
+public sealed class ReadFunction : LuaFunction
+{
+    public override string Name => "read";
+    public static readonly ReadFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var file = context.State.Environment["io"].Read<LuaTable>()["stdio"].Read<FileHandle>();
+        var resultCount = IOHelper.Read(file, Name, context, context.Arguments, buffer, cancellationToken);
+        return new(resultCount);
+    }
+}

+ 14 - 0
src/Lua/Standard/IO/WriteFunction.cs

@@ -0,0 +1,14 @@
+namespace Lua.Standard.IO;
+
+public sealed class WriteFunction : LuaFunction
+{
+    public override string Name => "write";
+    public static readonly WriteFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var file = context.State.Environment["io"].Read<LuaTable>()["stdio"].Read<FileHandle>();
+        var resultCount = IOHelper.Write(file, Name, context, buffer, cancellationToken);
+        return new(resultCount);
+    }
+}

+ 2 - 0
src/Lua/Standard/OpenLibExtensions.cs

@@ -78,6 +78,8 @@ public static class OpenLibExtensions
         CloseFunction.Instance,
         InputFunction.Instance,
         OutputFunction.Instance,
+        WriteFunction.Instance,
+        ReadFunction.Instance,
     ];
 
     public static void OpenBasicLibrary(this LuaState state)