using Lua.IO; using Lua.Runtime; using Lua.Standard.Internal; namespace Lua.Standard; public class FileHandle : ILuaUserData { public static readonly LuaFunction IndexMetamethod = new("index", (context, ct) => { context.GetArgument(0); var key = context.GetArgument(1); if (key.TryRead(out var name)) { return new(context.Return(name switch { "close" => CloseFunction!, "flush" => FlushFunction!, "lines" => LinesFunction!, "read" => ReadFunction!, "seek" => SeekFunction!, "setvbuf" => SetVBufFunction!, "write" => WriteFunction!, _ => LuaValue.Nil })); } else { return new(context.Return(LuaValue.Nil)); } }); ILuaStream stream; public bool IsOpen => stream?.IsOpen ?? false; LuaTable? ILuaUserData.Metatable { get => fileHandleMetatable; set => fileHandleMetatable = value; } static LuaTable? fileHandleMetatable; static FileHandle() { fileHandleMetatable = new(0, 1); fileHandleMetatable[Metamethods.Index] = IndexMetamethod; fileHandleMetatable["__tostring"] = ToStringFunction; } public FileHandle(Stream stream, LuaFileOpenMode mode) : this(ILuaStream.CreateFromStream(stream, mode)) { } public FileHandle(ILuaStream stream) { this.stream = stream; } public ValueTask ReadNumberAsync(CancellationToken cancellationToken) { return stream.ReadNumberAsync(cancellationToken); } public ValueTask ReadLineAsync(bool keepEol, CancellationToken cancellationToken) { return stream.ReadLineAsync(keepEol, cancellationToken); } public ValueTask ReadToEndAsync(CancellationToken cancellationToken) { return stream.ReadAllAsync(cancellationToken); } public ValueTask ReadStringAsync(int count, CancellationToken cancellationToken) { return stream.ReadAsync(count, cancellationToken); } public ValueTask WriteAsync(string content, CancellationToken cancellationToken) { return stream.WriteAsync(content.AsMemory(), cancellationToken); } public ValueTask WriteAsync(ReadOnlyMemory content, CancellationToken cancellationToken) { return stream.WriteAsync(content, cancellationToken); } public long Seek(string whence, long offset) { return whence switch { "set" => stream.Seek(SeekOrigin.Begin, offset), "cur" => stream.Seek(SeekOrigin.Current, offset), "end" => stream.Seek(SeekOrigin.End, offset), _ => throw new ArgumentException($"Invalid option '{whence}'") }; } public ValueTask FlushAsync(CancellationToken cancellationToken) { return stream.FlushAsync(cancellationToken); } public void SetVBuf(string mode, int size) { var bufferingMode = mode switch { "no" => LuaFileBufferingMode.NoBuffering, "full" => LuaFileBufferingMode.FullBuffering, "line" => LuaFileBufferingMode.LineBuffering, _ => throw new ArgumentException($"Invalid option '{mode}'") }; stream.SetVBuf(bufferingMode, size); } public async ValueTask Close(CancellationToken cancellationToken) { if (!stream.IsOpen) throw new ObjectDisposedException(nameof(FileHandle)); await stream.CloseAsync(cancellationToken); stream = null!; } static readonly LuaFunction CloseFunction = new("close", async (context, cancellationToken) => { var file = context.GetArgument(0); try { await file.Close(cancellationToken); return context.Return(true); } catch (IOException ex) { return context.Return(LuaValue.Nil, ex.Message, ex.HResult); } }); static readonly LuaFunction FlushFunction = new("file.flush", async (context, cancellationToken) => { var file = context.GetArgument(0); try { await file.FlushAsync(cancellationToken); return context.Return(true); } catch (IOException ex) { return context.Return(LuaValue.Nil, ex.Message, ex.HResult); } }); static readonly LuaFunction LinesFunction = new("file.lines", (context, cancellationToken) => { var file = context.GetArgument(0); var format = context.HasArgument(1) ? context.Arguments[1] : "*l"; return new(context.Return(new CSharpClosure("iterator", [new(file), format], static async (context, cancellationToken) => { var upValues = context.GetCsClosure()!.UpValues.AsMemory(); var file = upValues.Span[0].Read(); context.Return(); var resultCount = await IOHelper.ReadAsync(context.State, file, "file.lines", 0, upValues[1..], true, cancellationToken); return resultCount; }))); }); static readonly LuaFunction ReadFunction = new("file.read", async (context, cancellationToken) => { var file = context.GetArgument(0); var args = context.Arguments[1..].ToArray(); context.Return(); var resultCount = await IOHelper.ReadAsync(context.State, file, "file.read", 1, args, false, cancellationToken); return resultCount; }); static readonly LuaFunction SeekFunction = new("file.seek", (context, cancellationToken) => { var file = context.GetArgument(0); var whence = context.HasArgument(1) ? context.GetArgument(1) : "cur"; var offset = context.HasArgument(2) ? context.GetArgument(2) : 0; if (whence is not ("set" or "cur" or "end")) { throw new LuaRuntimeException(context.State, $"bad argument #2 to 'file.seek' (invalid option '{whence}')"); } try { return new(context.Return(file.Seek(whence, (long)offset))); } catch (IOException ex) { return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult)); } }); static readonly LuaFunction SetVBufFunction = new("file.setvbuf", (context, cancellationToken) => { var file = context.GetArgument(0); var mode = context.GetArgument(1); var size = context.HasArgument(2) ? context.GetArgument(2) : -1; file.SetVBuf(mode, size); return new(context.Return(true)); }); static readonly LuaFunction WriteFunction = new("file.write", async (context, cancellationToken) => { var file = context.GetArgument(0); var resultCount = await IOHelper.WriteAsync(file, "io.write", context with { ArgumentCount = context.ArgumentCount - 1 }, cancellationToken); return resultCount; }); static readonly LuaFunction ToStringFunction = new("file.__tostring", (context, cancellationToken) => { var file = context.GetArgument(0); return new(context.Return($"file ({(file.IsOpen ? file.stream.GetHashCode() : "closed")})")); }); }