Browse Source

Add: io.lines, file:lines

AnnulusGames 1 year ago
parent
commit
5811c201d2

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

@@ -17,6 +17,7 @@ public class FileHandle : LuaUserData
                 {
                 {
                     "write" => FileWriteFunction.Instance,
                     "write" => FileWriteFunction.Instance,
                     "read" => FileReadFunction.Instance,
                     "read" => FileReadFunction.Instance,
+                    "lines" => FileLinesFunction.Instance,
                     "flush" => FileFlushFunction.Instance,
                     "flush" => FileFlushFunction.Instance,
                     "close" => CloseFunction.Instance,
                     "close" => CloseFunction.Instance,
                     _ => LuaValue.Nil,
                     _ => LuaValue.Nil,

+ 29 - 0
src/Lua/Standard/IO/FileLinesFunction.cs

@@ -0,0 +1,29 @@
+namespace Lua.Standard.IO;
+
+public sealed class FileLinesFunction : LuaFunction
+{
+    public override string Name => "lines";
+    public static readonly FileLinesFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        var arg0 = context.ReadArgument<FileHandle>(0);
+        var arg1 = context.ArgumentCount >= 2
+            ? context.Arguments[1]
+            : "*l";
+        
+        buffer.Span[0] = new Iterator(arg0, arg1);
+        return new(1);
+    }
+
+    class Iterator(FileHandle file, LuaValue format) : LuaFunction
+    {
+        readonly LuaValue[] formats = [format];
+
+        protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+        {
+            var resultCount = IOHelper.Read(file, Name, 0, true, context, formats, buffer);
+            return new(resultCount);
+        }
+    }
+}

+ 1 - 1
src/Lua/Standard/IO/FileReadFunction.cs

@@ -12,7 +12,7 @@ public sealed class FileReadFunction : LuaFunction
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
     {
         var file = context.ReadArgument<FileHandle>(0);
         var file = context.ReadArgument<FileHandle>(0);
-        var resultCount = IOHelper.Read(file, Name, context, context.Arguments[1..], buffer);
+        var resultCount = IOHelper.Read(file, Name, 1, false, context, context.Arguments[1..], buffer);
         return new(resultCount);
         return new(resultCount);
     }
     }
 }
 }

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

@@ -6,6 +6,43 @@ namespace Lua.Standard.IO;
 
 
 internal static class IOHelper
 internal static class IOHelper
 {
 {
+    public static int Open(LuaState state, string fileName, string mode, Memory<LuaValue> buffer, bool throwError)
+    {
+        var fileMode = mode switch
+        {
+            "r" or "rb" or "r+" or "r+b" => FileMode.Open,
+            "w" or "wb" or "w+" or "w+b" => FileMode.Create,
+            "a" or "ab" or "a+" or "a+b" => FileMode.Append,
+            _ => throw new LuaRuntimeException(state.GetTraceback(), "bad argument #2 to 'open' (invalid mode)"),
+        };
+
+        var fileAccess = mode switch
+        {
+            "r" or "rb" => FileAccess.Read,
+            "w" or "wb" or "a" or "ab" => FileAccess.Write,
+            _ => FileAccess.ReadWrite,
+        };
+
+        try
+        {
+            var stream = File.Open(fileName, fileMode, fileAccess);
+            buffer.Span[0] = new FileHandle(stream);
+            return 1;
+        }
+        catch (IOException ex)
+        {
+            if (throwError)
+            {
+                throw;
+            }
+
+            buffer.Span[0] = LuaValue.Nil;
+            buffer.Span[1] = ex.Message;
+            buffer.Span[2] = ex.HResult;
+            return 3;
+        }
+    }
+
     // TODO: optimize (use IBuffertWrite<byte>, async)
     // TODO: optimize (use IBuffertWrite<byte>, async)
 
 
     public static int Write(FileHandle file, string name, LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     public static int Write(FileHandle file, string name, LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
@@ -50,7 +87,7 @@ internal static class IOHelper
 
 
     static readonly LuaValue[] defaultReadFormat = ["*l"];
     static readonly LuaValue[] defaultReadFormat = ["*l"];
 
 
-    public static int Read(FileHandle file, string name, LuaFunctionExecutionContext context, ReadOnlySpan<LuaValue> formats, Memory<LuaValue> buffer)
+    public static int Read(FileHandle file, string name, int startArgumentIndex, bool throwError, LuaFunctionExecutionContext context, ReadOnlySpan<LuaValue> formats, Memory<LuaValue> buffer)
     {
     {
         if (formats.Length == 0)
         if (formats.Length == 0)
         {
         {
@@ -89,7 +126,7 @@ internal static class IOHelper
                 {
                 {
                     if (!MathEx.IsInteger(d))
                     if (!MathEx.IsInteger(d))
                     {
                     {
-                        throw new LuaRuntimeException(context.State.GetTraceback(), $"bad argument #{i + 1} to 'read' (number has no integer representation)");
+                        throw new LuaRuntimeException(context.State.GetTraceback(), $"bad argument #{i + startArgumentIndex} to 'read' (number has no integer representation)");
                     }
                     }
 
 
                     var count = (int)d;
                     var count = (int)d;
@@ -119,6 +156,11 @@ internal static class IOHelper
         }
         }
         catch (IOException ex)
         catch (IOException ex)
         {
         {
+            if (throwError)
+            {
+                throw;
+            }
+
             buffer.Span[0] = LuaValue.Nil;
             buffer.Span[0] = LuaValue.Nil;
             buffer.Span[1] = ex.Message;
             buffer.Span[1] = ex.Message;
             buffer.Span[2] = ex.HResult;
             buffer.Span[2] = ex.HResult;

+ 45 - 0
src/Lua/Standard/IO/LinesFunction.cs

@@ -0,0 +1,45 @@
+using Lua.Internal;
+
+namespace Lua.Standard.IO;
+
+public sealed class LinesFunction : LuaFunction
+{
+    public override string Name => "lines";
+    public static readonly LinesFunction Instance = new();
+
+    protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+    {
+        if (context.ArgumentCount == 0)
+        {
+            var file = context.State.Environment["io"].Read<LuaTable>()["stdio"].Read<FileHandle>();
+            buffer.Span[0] = new Iterator(file, []);
+            return new(1);
+        }
+        else
+        {
+            var fileName = context.ReadArgument<string>(0);
+
+            using var methodBuffer = new PooledArray<LuaValue>(32);
+            IOHelper.Open(context.State, fileName, "r", methodBuffer.AsMemory(), true);
+
+            var file = methodBuffer[0].Read<FileHandle>();
+            buffer.Span[0] = new Iterator(file, context.Arguments[1..]);
+            return new(1);
+        }
+    }
+
+    class Iterator(FileHandle file, ReadOnlySpan<LuaValue> formats) : LuaFunction
+    {
+        readonly LuaValue[] formats = formats.ToArray();
+
+        protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
+        {
+            var resultCount = IOHelper.Read(file, Name, 0, true, context, formats, buffer);
+            if (resultCount > 0 && buffer.Span[0].Type is LuaValueType.Nil)
+            {
+                file.Close();
+            }
+            return new(resultCount);
+        }
+    }
+}

+ 2 - 28
src/Lua/Standard/IO/OpenFunction.cs

@@ -12,33 +12,7 @@ public sealed class OpenFunction : LuaFunction
             ? context.ReadArgument<string>(1)
             ? context.ReadArgument<string>(1)
             : "r";
             : "r";
 
 
-        var fileMode = mode switch
-        {
-            "r" or "rb" or "r+" or "r+b" => FileMode.Open,
-            "w" or "wb" or "w+" or "w+b" => FileMode.Create,
-            "a" or "ab" or "a+" or "a+b" => FileMode.Append,
-            _ => throw new LuaRuntimeException(context.State.GetTraceback(), "bad argument #2 to 'open' (invalid mode)"),
-        };
-
-        var fileAccess = mode switch
-        {
-            "r" or "rb" => FileAccess.Read,
-            "w" or "wb" or "a" or "ab" => FileAccess.Write,
-            _ => FileAccess.ReadWrite,
-        };
-
-        try
-        {
-            var stream = File.Open(fileName, fileMode, fileAccess);
-            buffer.Span[0] = new FileHandle(stream);
-            return new(1);
-        }
-        catch (IOException ex)
-        {
-            buffer.Span[0] = LuaValue.Nil;
-            buffer.Span[1] = ex.Message;
-            buffer.Span[2] = ex.HResult;
-            return new(3);
-        }
+        var resultCount = IOHelper.Open(context.State, fileName, mode, buffer, false);
+        return new(resultCount);
     }
     }
 }
 }

+ 1 - 1
src/Lua/Standard/IO/ReadFunction.cs

@@ -8,7 +8,7 @@ public sealed class ReadFunction : LuaFunction
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     protected override ValueTask<int> InvokeAsyncCore(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
     {
     {
         var file = context.State.Environment["io"].Read<LuaTable>()["stdio"].Read<FileHandle>();
         var file = context.State.Environment["io"].Read<LuaTable>()["stdio"].Read<FileHandle>();
-        var resultCount = IOHelper.Read(file, Name, context, context.Arguments, buffer);
+        var resultCount = IOHelper.Read(file, Name, 0, false, context, context.Arguments, buffer);
         return new(resultCount);
         return new(resultCount);
     }
     }
 }
 }

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

@@ -80,6 +80,7 @@ public static class OpenLibExtensions
         OutputFunction.Instance,
         OutputFunction.Instance,
         WriteFunction.Instance,
         WriteFunction.Instance,
         ReadFunction.Instance,
         ReadFunction.Instance,
+        LinesFunction.Instance,
         IO.TypeFunction.Instance,
         IO.TypeFunction.Instance,
     ];
     ];