From 35c654dfba57a94870bdeb96845bcb46672f69f8 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Mon, 19 May 2025 01:47:47 +0900 Subject: [PATCH 01/33] feat: enhance module loading and package management --- src/Lua/LuaState.cs | 8 +- src/Lua/Standard/ModuleLibrary.cs | 120 +++++++++++++++++++++++-- src/Lua/Standard/OpenLibsExtensions.cs | 19 ++-- 3 files changed, 131 insertions(+), 16 deletions(-) diff --git a/src/Lua/LuaState.cs b/src/Lua/LuaState.cs index 2fe3a84a..e2a881d4 100644 --- a/src/Lua/LuaState.cs +++ b/src/Lua/LuaState.cs @@ -4,8 +4,8 @@ using Lua.Internal; using Lua.Loaders; using Lua.Runtime; +using Lua.Standard; using System.Buffers; -using System.Text; namespace Lua; @@ -15,7 +15,6 @@ public sealed class LuaState readonly LuaMainThread mainThread; FastListCore openUpValues; FastStackCore threadStack; - readonly LuaTable packages = new(); readonly LuaTable environment; readonly LuaTable registry = new(); readonly UpValue envUpValue; @@ -31,7 +30,8 @@ public sealed class LuaState public LuaTable Environment => environment; public LuaTable Registry => registry; - public LuaTable LoadedModules => packages; + public LuaTable LoadedModules => registry[ModuleLibrary.LoadedKeyForRegistry].Read(); + public LuaTable PreloadModules => registry[ModuleLibrary.PreloadKeyForRegistry].Read(); public LuaMainThread MainThread => mainThread; public LuaThreadAccess TopLevelAccess => new (mainThread, 0); @@ -56,6 +56,8 @@ public static LuaState Create() mainThread = new(this); environment = new(); envUpValue = UpValue.Closed(environment); + registry[ModuleLibrary.LoadedKeyForRegistry] = new LuaTable(0, 8); + registry[ModuleLibrary.PreloadKeyForRegistry] = new LuaTable(0, 8); } diff --git a/src/Lua/Standard/ModuleLibrary.cs b/src/Lua/Standard/ModuleLibrary.cs index c74d41e7..3608829c 100644 --- a/src/Lua/Standard/ModuleLibrary.cs +++ b/src/Lua/Standard/ModuleLibrary.cs @@ -5,13 +5,17 @@ namespace Lua.Standard; public sealed class ModuleLibrary { public static readonly ModuleLibrary Instance = new(); + internal const string LoadedKeyForRegistry = "_LOADED"; + internal const string PreloadKeyForRegistry = "_PRELOAD"; public ModuleLibrary() { RequireFunction = new("require", Require); + SearchPathFunction = new("searchpath", SearchPath); } public readonly LuaFunction RequireFunction; + public readonly LuaFunction SearchPathFunction; public async ValueTask Require(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { @@ -20,18 +24,118 @@ public async ValueTask Require(LuaFunctionExecutionContext context, Cancell if (!loaded.TryGetValue(arg0, out var loadedTable)) { - LuaClosure closure; - { - using var module = await context.State.ModuleLoader.LoadAsync(arg0, cancellationToken); - closure = module.Type == LuaModuleType.Bytes - ? context.State.Load(module.ReadBytes(), module.Name) - : context.State.Load(module.ReadText(), module.Name); - } - await context.Access.RunAsync(closure, 0, context.ReturnFrameBase, cancellationToken); + var loader = await FindLoader(context.Access, arg0, cancellationToken); + await context.Access.RunAsync(loader, 0, context.ReturnFrameBase, cancellationToken); loadedTable = context.Thread.Stack.Get(context.ReturnFrameBase); loaded[arg0] = loadedTable; } return context.Return(loadedTable); } + + internal static async ValueTask FindFile(LuaThreadAccess access, string name, string pName, string dirSeparator) + { + var thread = access.Thread; + var state = thread.State; + var package = state.Environment["package"]; + var p = await access.GetTable(package, pName); + if (!p.TryReadString(out var path)) + { + throw new LuaRuntimeException(thread, ($"package.{pName} must be a string")); + } + + return SearchPath(state, name, path, ".", dirSeparator); + } + + public ValueTask SearchPath(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var name = context.GetArgument(0); + var path = context.GetArgument(1); + var separator = context.GetArgument(2); + var dirSeparator = context.GetArgument(3); + var fileName = SearchPath(context.State, name, path, separator, dirSeparator); + return new(context.Return(fileName ?? LuaValue.Nil)); + } + + internal static string? SearchPath(LuaState state, string name, string path, string separator, string dirSeparator) + { + if (separator != "") + { + name = name.Replace(separator, dirSeparator); + } + + var pathSpan = path.AsSpan(); + var nextIndex = pathSpan.IndexOf(';'); + if (nextIndex == -1) nextIndex = pathSpan.Length; + do + { + path = pathSpan[..nextIndex].ToString(); + var fileName = path.Replace("?", name); + if (File.Exists(fileName)) + { + return fileName; + } + + if (pathSpan.Length <= nextIndex) break; + pathSpan = pathSpan[(nextIndex + 1)..]; + nextIndex = pathSpan.IndexOf(';'); + if (nextIndex == -1) nextIndex = pathSpan.Length; + } while (nextIndex != -1); + + return null; + } + + internal static async ValueTask FindLoader(LuaThreadAccess access, string name, CancellationToken cancellationToken) + { + var state = access.State; + var package = state.Environment["package"].Read(); + var searchers = package["searchers"].Read(); + for (int i = 0; i < searchers.GetArraySpan().Length; i++) + { + var searcher = searchers.GetArraySpan()[i]; + if (searcher.Type == LuaValueType.Nil) continue; + var loader = searcher; + var top = access.Stack.Count; + access.Stack.Push(loader); + access.Stack.Push(name); + var resultCount = await access.Call(top, top, cancellationToken); + if (0 < resultCount) + { + var result = access.Stack.Get(top); + if (result.Type == LuaValueType.Function) + { + access.Stack.SetTop(top); + return result.Read(); + } + } + + access.Stack.SetTop(top); + } + + throw new LuaRuntimeException(access.Thread, ($"Module '{name}' not found")); + } + + public ValueTask SearcherPreload(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var name = context.GetArgument(0); + var preload = context.State.PreloadModules[name]; + if (preload == LuaValue.Nil) + { + return new(context.Return()); + } + + return new(context.Return(preload)); + } + + public async ValueTask SearcherLua(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var name = context.GetArgument(0); + var fileName = await FindFile(context.Access, name, "path", Path.DirectorySeparatorChar.ToString()); + if (fileName == null) + { + return (context.Return(LuaValue.Nil)); + } + + return context.Return(await context.State.LoadFileAsync(fileName, "bt", null, cancellationToken)); + } } \ No newline at end of file diff --git a/src/Lua/Standard/OpenLibsExtensions.cs b/src/Lua/Standard/OpenLibsExtensions.cs index 0f258ab8..09b10422 100644 --- a/src/Lua/Standard/OpenLibsExtensions.cs +++ b/src/Lua/Standard/OpenLibsExtensions.cs @@ -47,9 +47,9 @@ public static void OpenIOLibrary(this LuaState state) } var registry = state.Registry; - registry ["stdin"] = new (new FileHandle(ConsoleHelper.OpenStandardInput())); - registry["stdout"] =new (new FileHandle(ConsoleHelper.OpenStandardOutput())); - registry["stderr"] = new (new FileHandle(ConsoleHelper.OpenStandardError())); + registry["stdin"] = new(new FileHandle(ConsoleHelper.OpenStandardInput())); + registry["stdout"] = new(new FileHandle(ConsoleHelper.OpenStandardOutput())); + registry["stderr"] = new(new FileHandle(ConsoleHelper.OpenStandardError())); state.Environment["io"] = io; state.LoadedModules["io"] = io; @@ -74,10 +74,19 @@ public static void OpenMathLibrary(this LuaState state) public static void OpenModuleLibrary(this LuaState state) { - var package = new LuaTable(); + var package = new LuaTable(0, 8); package["loaded"] = state.LoadedModules; + package["preload"] = state.PreloadModules; + var moduleLibrary = ModuleLibrary.Instance; + var searchers = new LuaTable(); + searchers[1] = new LuaFunction("preload", moduleLibrary.SearcherPreload); + searchers[2] = new LuaFunction("searcher_Lua", moduleLibrary.SearcherLua); + package["searchers"] = searchers; + package["path"] = "?.lua"; + package["searchpath"] = moduleLibrary.SearchPathFunction; + package["config"] = $"{Path.DirectorySeparatorChar}\n;\n?\n!\n-"; state.Environment["package"] = package; - state.Environment["require"] = ModuleLibrary.Instance.RequireFunction; + state.Environment["require"] = moduleLibrary.RequireFunction; } public static void OpenOperatingSystemLibrary(this LuaState state) From 39be24e2c7262a37d897f2e36ad5a2d84958af6e Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Mon, 19 May 2025 01:50:15 +0900 Subject: [PATCH 02/33] change: abstract async file and stream system --- src/Lua/ILuaFileManager.cs | 167 +++++++++++++++++++ src/Lua/LuaFile.cs | 71 ++++++++ src/Lua/LuaState.cs | 2 + src/Lua/LuaStateExtensions.cs | 13 ++ src/Lua/Runtime/LuaThreadAccessExtensions.cs | 8 +- src/Lua/Standard/BasicLibrary.cs | 13 +- src/Lua/Standard/FileHandle.cs | 81 ++++----- src/Lua/Standard/IOLibrary.cs | 41 +++-- src/Lua/Standard/Internal/IOHelper.cs | 20 +-- src/Lua/Standard/ModuleLibrary.cs | 2 +- src/Lua/Standard/OperatingSystemLibrary.cs | 4 +- 11 files changed, 335 insertions(+), 87 deletions(-) create mode 100644 src/Lua/ILuaFileManager.cs create mode 100644 src/Lua/LuaFile.cs diff --git a/src/Lua/ILuaFileManager.cs b/src/Lua/ILuaFileManager.cs new file mode 100644 index 00000000..4746e489 --- /dev/null +++ b/src/Lua/ILuaFileManager.cs @@ -0,0 +1,167 @@ +namespace Lua; + +public interface ILuaFileManager +{ + public bool IsReadable(string path); + public ValueTask LoadModuleAsync(string path, CancellationToken cancellationToken); + public IStream Open(string path, FileMode mode, FileAccess access); + public void Rename (string oldName, string newName); + public void Remove (string path); +} + + +public interface IStream : IDisposable +{ + public IStreamReader? Reader { get; } + public IStreamWriter? Writer { get; } + + public long Seek(long offset, SeekOrigin origin); + + public void SetLength(long value); + + public bool CanRead { get; } + public bool CanSeek { get; } + public bool CanWrite { get; } + public long Length { get; } + public long Position { get; set; } +} + +public interface IStreamReader : IDisposable +{ + public ValueTask ReadLineAsync(CancellationToken cancellationToken); + public ValueTask ReadToEndAsync(CancellationToken cancellationToken); + public ValueTask ReadByteAsync(CancellationToken cancellationToken); +} + +public interface IStreamWriter : IDisposable +{ + public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken); + public ValueTask FlushAsync(CancellationToken cancellationToken); + + public void SetVBuf(string mode, int size); +} + +public sealed class SystemFileManager : ILuaFileManager +{ + public static readonly SystemFileManager Instance = new(); + + 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; + } + } + + public ValueTask LoadModuleAsync(string path, CancellationToken cancellationToken) + { + var bytes = File.ReadAllBytes(path); + return new(new LuaFile(bytes)); + } + + public IStream Open(string path, FileMode mode, FileAccess access) + { + return new SystemStream(File.Open(path, mode, access)); + } + + public ValueTask WriteAllTextAsync(string path, string text, CancellationToken cancellationToken) + { + File.WriteAllText(path, text); + return new(); + } + 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); + } +} + +public sealed class SystemStreamReader(StreamReader streamReader) : IStreamReader +{ + public ValueTask ReadLineAsync(CancellationToken cancellationToken) + { + return new(streamReader.ReadLine()); + } + + public ValueTask ReadToEndAsync(CancellationToken cancellationToken) + { + return new(streamReader.ReadToEnd()); + } + + public ValueTask ReadByteAsync(CancellationToken cancellationToken) + { + return new(streamReader.Read()); + } + + public void Dispose() + { + streamReader.Dispose(); + } +} + +public sealed class SystemStreamWriter(StreamWriter streamWriter) : IStreamWriter +{ + public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + streamWriter.Write(buffer.Span); + return new(); + } + + public ValueTask WriteLineAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + streamWriter.WriteLine(buffer.Span); + return new(); + } + + public ValueTask FlushAsync(CancellationToken cancellationToken) + { + streamWriter.Flush(); + return new(); + } + + public void SetVBuf(string mode, int size) + { + // Ignore size parameter + streamWriter.AutoFlush = mode is "no" or "line"; + } + + public void Dispose() + { + streamWriter.Dispose(); + } +} + +public sealed class SystemStream(Stream fileStream) : IStream +{ + public IStreamReader? Reader => fileStream.CanRead ? new SystemStreamReader(new(fileStream)) : null; + public IStreamWriter? Writer => fileStream.CanWrite ? new SystemStreamWriter(new(fileStream)) : null; + + public long Seek(long offset, SeekOrigin origin) => fileStream.Seek(offset, origin); + + public void SetLength(long value) => fileStream.SetLength(value); + + public bool CanRead => fileStream.CanRead; + public bool CanSeek => fileStream.CanSeek; + public bool CanWrite => fileStream.CanWrite; + public long Length => fileStream.Length; + + public long Position + { + get => fileStream.Position; + set => fileStream.Position = value; + } + + public void Dispose() => fileStream.Dispose(); +} \ No newline at end of file diff --git a/src/Lua/LuaFile.cs b/src/Lua/LuaFile.cs new file mode 100644 index 00000000..ffc6317a --- /dev/null +++ b/src/Lua/LuaFile.cs @@ -0,0 +1,71 @@ +using System.Buffers; + +namespace Lua; + +public enum LuaFileType +{ + Text, + Bytes +} + +public readonly struct LuaFile : IDisposable +{ + public LuaFileType Type => type; + + readonly LuaFileType type; + readonly object referenceValue; + + public LuaFile(string text) + { + type = LuaFileType.Text; + referenceValue = text; + } + + public LuaFile(byte[] bytes) + { + type = LuaFileType.Bytes; + referenceValue = bytes; + } + + public LuaFile(IMemoryOwner bytes) + { + type = LuaFileType.Text; + referenceValue = bytes; + } + + public LuaFile(IMemoryOwner bytes) + { + type = LuaFileType.Bytes; + referenceValue = bytes; + } + + public ReadOnlySpan ReadText() + { + if (type != LuaFileType.Text) throw new Exception(); // TODO: add message + if (referenceValue is IMemoryOwner mem) + { + return mem.Memory.Span; + } + + return ((string)referenceValue); + } + + public ReadOnlySpan ReadBytes() + { + if (type != LuaFileType.Bytes) throw new Exception(); // TODO: add message + if (referenceValue is IMemoryOwner mem) + { + return mem.Memory.Span; + } + + return (byte[])referenceValue; + } + + public void Dispose() + { + if (referenceValue is IDisposable memoryOwner) + { + memoryOwner.Dispose(); + } + } +} \ No newline at end of file diff --git a/src/Lua/LuaState.cs b/src/Lua/LuaState.cs index e2a881d4..9ee400f4 100644 --- a/src/Lua/LuaState.cs +++ b/src/Lua/LuaState.cs @@ -37,6 +37,8 @@ public sealed class LuaState public LuaThreadAccess TopLevelAccess => new (mainThread, 0); public ILuaModuleLoader ModuleLoader { get; set; } = FileModuleLoader.Instance; + + public ILuaFileManager FileManager { get; set; } = SystemFileManager.Instance; // metatables LuaTable? nilMetatable; diff --git a/src/Lua/LuaStateExtensions.cs b/src/Lua/LuaStateExtensions.cs index f67e32bc..bc24ce61 100644 --- a/src/Lua/LuaStateExtensions.cs +++ b/src/Lua/LuaStateExtensions.cs @@ -23,4 +23,17 @@ public static ValueTask DoFileAsync(this LuaState state, string path { return state.TopLevelAccess.DoFileAsync(path, cancellationToken); } + + public static async ValueTask LoadFileAsync(this LuaState state, string fileName, string mode, LuaTable? environment, CancellationToken cancellationToken) + { + var name = "@" + fileName; + LuaClosure closure; + { + using var file = await state.FileManager.LoadModuleAsync(fileName, cancellationToken); + closure = file.Type == LuaFileType.Bytes + ? state.Load(file.ReadBytes(), name, mode, environment) + : state.Load(file.ReadText(), name, environment); + } + return closure; + } } \ No newline at end of file diff --git a/src/Lua/Runtime/LuaThreadAccessExtensions.cs b/src/Lua/Runtime/LuaThreadAccessExtensions.cs index a197f140..2e6f7168 100644 --- a/src/Lua/Runtime/LuaThreadAccessExtensions.cs +++ b/src/Lua/Runtime/LuaThreadAccessExtensions.cs @@ -28,9 +28,7 @@ public static async ValueTask DoStringAsync(this LuaThreadAccess acc public static async ValueTask DoFileAsync(this LuaThreadAccess access, string path, Memory buffer, CancellationToken cancellationToken = default) { access.ThrowIfInvalid(); - var bytes = File.ReadAllBytes(path); - var fileName = "@" + path; - var closure = access.State.Load(bytes, fileName); + var closure = await access.State.LoadFileAsync(path, "bt", null, cancellationToken); var count = await access.RunAsync(closure, 0, cancellationToken); using var results = access.ReadReturnValues(count); results.AsSpan()[..Math.Min(buffer.Length, results.Length)].CopyTo(buffer.Span); @@ -39,9 +37,7 @@ public static async ValueTask DoFileAsync(this LuaThreadAccess access, stri public static async ValueTask DoFileAsync(this LuaThreadAccess access, string path, CancellationToken cancellationToken = default) { - var bytes = File.ReadAllBytes(path); - var fileName = "@" + path; - var closure = access.State.Load(bytes, fileName); + var closure = await access.State.LoadFileAsync(path, "bt", null, cancellationToken); var count = await access.RunAsync(closure, 0, cancellationToken); using var results = access.ReadReturnValues(count); return results.AsSpan().ToArray(); diff --git a/src/Lua/Standard/BasicLibrary.cs b/src/Lua/Standard/BasicLibrary.cs index f7046f05..506c8e1c 100644 --- a/src/Lua/Standard/BasicLibrary.cs +++ b/src/Lua/Standard/BasicLibrary.cs @@ -89,10 +89,7 @@ public async ValueTask DoFile(LuaFunctionExecutionContext context, Cancella { var arg0 = context.GetArgument(0); context.Thread.Stack.PopUntil(context.ReturnFrameBase); - - var bytes = File.ReadAllBytes(arg0); - var fileName = "@" + arg0; - var closure = context.State.Load(bytes, fileName); + var closure = await context.State.LoadFileAsync(arg0, "bt",null, cancellationToken); return await context.Access.RunAsync(closure, cancellationToken); } @@ -148,7 +145,7 @@ public async ValueTask IPairs(LuaFunctionExecutionContext context, Cancella return context.Return(IPairsIterator, arg0, 0); } - public ValueTask LoadFile(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + public async ValueTask LoadFile(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var arg0 = context.GetArgument(0); var mode = context.HasArgument(1) @@ -161,13 +158,11 @@ public ValueTask LoadFile(LuaFunctionExecutionContext context, Cancellation // do not use LuaState.DoFileAsync as it uses the newExecutionContext try { - var bytes = File.ReadAllBytes(arg0); - var fileName = "@" + arg0; - return new(context.Return(context.State.Load(bytes, fileName, mode, arg2))); + return context.Return(await context.State.LoadFileAsync(arg0, mode, arg2,cancellationToken)); } catch (Exception ex) { - return new(context.Return(LuaValue.Nil, ex.Message)); + return context.Return(LuaValue.Nil, ex.Message); } } diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index b8cb25a8..74dd9c73 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -32,9 +32,9 @@ public class FileHandle : ILuaUserData } }); - Stream stream; - StreamWriter? writer; - StreamReader? reader; + IStream stream; + IStreamWriter? writer; + IStreamReader? reader; bool isClosed; public bool IsClosed => Volatile.Read(ref isClosed); @@ -49,31 +49,33 @@ static FileHandle() fileHandleMetatable[Metamethods.Index] = IndexMetamethod; } - public FileHandle(Stream stream) + public FileHandle(Stream stream) : this(new SystemStream(stream)) { } + + public FileHandle(IStream stream) { this.stream = stream; - if (stream.CanRead) reader = new StreamReader(stream); - if (stream.CanWrite) writer = new StreamWriter(stream); + if (stream.CanRead) reader = stream.Reader; + if (stream.CanWrite) writer = stream.Writer; } - public string? ReadLine() + public ValueTask ReadLineAsync(CancellationToken cancellationToken) { - return reader!.ReadLine(); + return reader!.ReadLineAsync(cancellationToken); } - public string ReadToEnd() + public ValueTask ReadToEndAsync(CancellationToken cancellationToken) { - return reader!.ReadToEnd(); + return reader!.ReadToEndAsync(cancellationToken); } - public int ReadByte() + public ValueTask ReadByteAsync(CancellationToken cancellationToken) { - return stream.ReadByte(); + return reader!.ReadByteAsync(cancellationToken); } - public void Write(ReadOnlySpan buffer) + public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) { - writer!.Write(buffer); + return writer!.WriteAsync(buffer, cancellationToken); } public long Seek(string whence, long offset) @@ -99,19 +101,14 @@ public long Seek(string whence, long offset) return stream.Position; } - public void Flush() + public ValueTask FlushAsync(CancellationToken cancellationToken) { - writer!.Flush(); + return writer!.FlushAsync(cancellationToken); } public void SetVBuf(string mode, int size) { - // Ignore size parameter - - if (writer != null) - { - writer.AutoFlush = mode is "no" or "line"; - } + writer!.SetVBuf(mode, size); } public void Close() @@ -123,10 +120,18 @@ public void Close() { reader.Dispose(); } + else if (writer != null) + { + writer.Dispose(); + } else { - stream.Close(); + stream.Dispose(); } + + stream = null!; + writer = null; + reader = null; } static readonly LuaFunction CloseFunction = new("close", (context, cancellationToken) => @@ -144,18 +149,18 @@ public void Close() } }); - static readonly LuaFunction FlushFunction = new("flush", (context, cancellationToken) => + static readonly LuaFunction FlushFunction = new("flush", async (context, cancellationToken) => { var file = context.GetArgument(0); try { - file.Flush(); - return new(context.Return(true)); + await file.FlushAsync(cancellationToken); + return context.Return(true); } catch (IOException ex) { - return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult)); + return (context.Return(LuaValue.Nil, ex.Message, ex.HResult)); } }); @@ -167,22 +172,22 @@ public void Close() : "*l"; - return new(context.Return(new CSharpClosure("iterator", [new(file), format], static (context, cancellationToken) => + return new(context.Return(new CSharpClosure("iterator", [new(file), format], static async (context, cancellationToken) => { - var upValues = context.GetCsClosure()!.UpValues.AsSpan(); - var file = upValues[0].Read(); + var upValues = context.GetCsClosure()!.UpValues.AsMemory(); + var file = upValues.Span[0].Read(); context.Return(); - var resultCount = IOHelper.Read(context.Thread, file, "lines", 0, upValues[1..], true); - return new(resultCount); + var resultCount = await IOHelper.ReadAsync(context.Thread, file, "lines", 0, upValues[1..], true, cancellationToken); + return resultCount; }))); }); - static readonly LuaFunction ReadFunction = new("read", (context, cancellationToken) => + static readonly LuaFunction ReadFunction = new("read", async (context, cancellationToken) => { var file = context.GetArgument(0); context.Return(); - var resultCount = IOHelper.Read(context.Thread, file, "read", 1, context.Arguments[1..], false); - return new(resultCount); + var resultCount = await IOHelper.ReadAsync(context.Thread, file, "read", 1, context.Arguments[1..].ToArray(), false, cancellationToken); + return resultCount; }); static readonly LuaFunction SeekFunction = new("seek", (context, cancellationToken) => @@ -223,10 +228,10 @@ public void Close() return new(context.Return(true)); }); - static readonly LuaFunction WriteFunction = new("write", (context, cancellationToken) => + static readonly LuaFunction WriteFunction = new("write", async (context, cancellationToken) => { var file = context.GetArgument(0); - var resultCount = IOHelper.Write(file, "write", context with{ArgumentCount = context.ArgumentCount-1}); - return new(resultCount); + var resultCount = await IOHelper.WriteAsync(file, "write", context with { ArgumentCount = context.ArgumentCount - 1 }, cancellationToken); + return resultCount; }); } \ No newline at end of file diff --git a/src/Lua/Standard/IOLibrary.cs b/src/Lua/Standard/IOLibrary.cs index d8684f2a..e206697a 100644 --- a/src/Lua/Standard/IOLibrary.cs +++ b/src/Lua/Standard/IOLibrary.cs @@ -42,18 +42,18 @@ public ValueTask Close(LuaFunctionExecutionContext context, CancellationTok } } - public ValueTask Flush(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + public async ValueTask Flush(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var file = context.State.Registry["stdout"].Read(); try { - file.Flush(); - return new(context.Return(true)); + await file.FlushAsync(cancellationToken); + return context.Return(true); } catch (IOException ex) { - return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult)); + return context.Return(LuaValue.Nil, ex.Message, ex.HResult); } } @@ -63,7 +63,7 @@ public ValueTask Input(LuaFunctionExecutionContext context, CancellationTok if (context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil) { - return new(context.Return( registry["stdin"])); + return new(context.Return(registry["stdin"])); } var arg = context.Arguments[0]; @@ -74,7 +74,7 @@ public ValueTask Input(LuaFunctionExecutionContext context, CancellationTok } else { - var stream = File.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); + var stream = context.State.FileManager.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); var handle = new FileHandle(stream); registry["stdin"] = new(handle); return new(context.Return(new LuaValue(handle))); @@ -86,17 +86,17 @@ public ValueTask Lines(LuaFunctionExecutionContext context, CancellationTok if (context.ArgumentCount == 0) { var file = context.State.Registry["stdin"].Read(); - return new(context.Return(new CSharpClosure("iterator", [new(file)], static (context, ct) => + return new(context.Return(new CSharpClosure("iterator", [new(file)], static async (context, cancellationToken) => { var file = context.GetCsClosure()!.UpValues[0].Read(); context.Return(); - var resultCount = IOHelper.Read(context.Thread, file, "lines", 0, [], true); + var resultCount = await IOHelper.ReadAsync(context.Thread, file, "lines", 0, Memory.Empty, true, cancellationToken); if (resultCount > 0 && context.Thread.Stack.Get(context.ReturnFrameBase).Type is LuaValueType.Nil) { file.Close(); } - return new(resultCount); + return resultCount; }))); } else @@ -112,20 +112,20 @@ public ValueTask Lines(LuaFunctionExecutionContext context, CancellationTok upValues[0] = new(file); context.Arguments[1..].CopyTo(upValues[1..]); - return new(context.Return(new CSharpClosure("iterator", upValues, static (context, ct) => + return new(context.Return(new CSharpClosure("iterator", upValues, static async (context, cancellationToken) => { var upValues = context.GetCsClosure()!.UpValues; var file = upValues[0].Read(); - var formats = upValues.AsSpan(1); + var formats = upValues.AsMemory(1); var stack = context.Thread.Stack; context.Return(); - var resultCount = IOHelper.Read(context.Thread, file, "lines", 0, formats, true); + var resultCount = await IOHelper.ReadAsync(context.Thread, file, "lines", 0, formats, true, cancellationToken); if (resultCount > 0 && stack.Get(context.ReturnFrameBase).Type is LuaValueType.Nil) { file.Close(); } - return new(resultCount); + return resultCount; }))); } } @@ -158,21 +158,20 @@ public ValueTask Output(LuaFunctionExecutionContext context, CancellationTo } else { - var stream = File.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); + var stream = context.State.FileManager.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); var handle = new FileHandle(stream); io["stdout"] = new(handle); return new(context.Return(new LuaValue(handle))); } } - public ValueTask Read(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + public async ValueTask Read(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var file = context.State.Registry["stdin"].Read(); context.Return(); - var stack = context.Thread.Stack; - var resultCount = IOHelper.Read(context.Thread, file, "read", 0, context.Arguments, false); - return new(resultCount); + var resultCount = await IOHelper.ReadAsync(context.Thread, file, "read", 0, context.Arguments.ToArray(), false, cancellationToken); + return resultCount; } public ValueTask Type(LuaFunctionExecutionContext context, CancellationToken cancellationToken) @@ -189,10 +188,10 @@ public ValueTask Type(LuaFunctionExecutionContext context, CancellationToke } } - public ValueTask Write(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + public async ValueTask Write(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var file = context.State.Registry["stdout"].Read(); - var resultCount = IOHelper.Write(file, "write", context); - return new(resultCount); + var resultCount = await IOHelper.WriteAsync(file, "write", context, cancellationToken); + return resultCount; } } \ No newline at end of file diff --git a/src/Lua/Standard/Internal/IOHelper.cs b/src/Lua/Standard/Internal/IOHelper.cs index 5e4d36d0..ebe784e9 100644 --- a/src/Lua/Standard/Internal/IOHelper.cs +++ b/src/Lua/Standard/Internal/IOHelper.cs @@ -24,7 +24,7 @@ public static int Open(LuaThread thread, string fileName, string mode, bool thro try { - var stream = File.Open(fileName, fileMode, fileAccess); + var stream = thread.State.FileManager.Open(fileName, fileMode, fileAccess); thread.Stack.Push(new LuaValue(new FileHandle(stream))); return 1; } @@ -44,7 +44,7 @@ public static int Open(LuaThread thread, string fileName, string mode, bool thro // TODO: optimize (use IBuffertWrite, async) - public static int Write(FileHandle file, string name, LuaFunctionExecutionContext context) + public static async ValueTask WriteAsync(FileHandle file, string name, LuaFunctionExecutionContext context, CancellationToken cancellationToken) { try { @@ -53,14 +53,14 @@ public static int Write(FileHandle file, string name, LuaFunctionExecutionContex var arg = context.Arguments[i]; if (arg.TryRead(out var str)) { - file.Write(str); + await file.WriteAsync(str.AsMemory(), cancellationToken); } else if (arg.TryRead(out var d)) { using var fileBuffer = new PooledArray(64); var span = fileBuffer.AsSpan(); d.TryFormat(span, out var charsWritten); - file.Write(span[..charsWritten]); + await file.WriteAsync(fileBuffer.AsMemory()[..charsWritten], cancellationToken); } else { @@ -85,7 +85,7 @@ public static int Write(FileHandle file, string name, LuaFunctionExecutionContex static readonly LuaValue[] defaultReadFormat = ["*l"]; - public static int Read(LuaThread thread, FileHandle file, string name, int startArgumentIndex, ReadOnlySpan formats, bool throwError) + public static async ValueTask ReadAsync(LuaThread thread, FileHandle file, string name, int startArgumentIndex, ReadOnlyMemory formats, bool throwError, CancellationToken cancellationToken) { if (formats.Length == 0) { @@ -99,7 +99,7 @@ public static int Read(LuaThread thread, FileHandle file, string name, int start { for (int i = 0; i < formats.Length; i++) { - var format = formats[i]; + var format = formats.Span[i]; if (format.TryRead(out var str)) { switch (str) @@ -110,15 +110,15 @@ public static int Read(LuaThread thread, FileHandle file, string name, int start throw new NotImplementedException(); case "*a": case "*all": - stack.Push(file.ReadToEnd()); + stack.Push(await file.ReadToEndAsync(cancellationToken)); break; case "*l": case "*line": - stack.Push(file.ReadLine() ?? LuaValue.Nil); + stack.Push(await file.ReadLineAsync(cancellationToken) ?? LuaValue.Nil); break; case "L": case "*L": - var text = file.ReadLine(); + var text = await file.ReadLineAsync(cancellationToken); stack.Push(text == null ? LuaValue.Nil : text + Environment.NewLine); break; } @@ -129,7 +129,7 @@ public static int Read(LuaThread thread, FileHandle file, string name, int start for (int j = 0; j < count; j++) { - var b = file.ReadByte(); + var b = await file.ReadByteAsync(cancellationToken); if (b == -1) { stack.PopUntil(top); diff --git a/src/Lua/Standard/ModuleLibrary.cs b/src/Lua/Standard/ModuleLibrary.cs index 3608829c..cacc919e 100644 --- a/src/Lua/Standard/ModuleLibrary.cs +++ b/src/Lua/Standard/ModuleLibrary.cs @@ -71,7 +71,7 @@ public ValueTask SearchPath(LuaFunctionExecutionContext context, Cancellati { path = pathSpan[..nextIndex].ToString(); var fileName = path.Replace("?", name); - if (File.Exists(fileName)) + if (state.FileManager.IsReadable(fileName)) { return fileName; } diff --git a/src/Lua/Standard/OperatingSystemLibrary.cs b/src/Lua/Standard/OperatingSystemLibrary.cs index 5ae5cce3..fd2065ca 100644 --- a/src/Lua/Standard/OperatingSystemLibrary.cs +++ b/src/Lua/Standard/OperatingSystemLibrary.cs @@ -143,7 +143,7 @@ public ValueTask Remove(LuaFunctionExecutionContext context, CancellationTo var fileName = context.GetArgument(0); try { - File.Delete(fileName); + context.State.FileManager.Remove(fileName); return new(context.Return(true)); } catch (IOException ex) @@ -158,7 +158,7 @@ public ValueTask Rename(LuaFunctionExecutionContext context, CancellationTo var newName = context.GetArgument(1); try { - File.Move(oldName, newName); + context.State.FileManager.Rename(oldName, newName); return new(context.Return(true)); } catch (IOException ex) From 1c778d2762b1867798e1078e1d584fed40f7ccc9 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Mon, 19 May 2025 07:40:09 +0900 Subject: [PATCH 03/33] rename: rename LoadModuleAsync to ReadFileAsync --- src/Lua/ILuaFileManager.cs | 4 ++-- src/Lua/LuaStateExtensions.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Lua/ILuaFileManager.cs b/src/Lua/ILuaFileManager.cs index 4746e489..31c2aad1 100644 --- a/src/Lua/ILuaFileManager.cs +++ b/src/Lua/ILuaFileManager.cs @@ -3,7 +3,7 @@ public interface ILuaFileManager { public bool IsReadable(string path); - public ValueTask LoadModuleAsync(string path, CancellationToken cancellationToken); + public ValueTask ReadFileAsync(string path, CancellationToken cancellationToken); public IStream Open(string path, FileMode mode, FileAccess access); public void Rename (string oldName, string newName); public void Remove (string path); @@ -59,7 +59,7 @@ public bool IsReadable(string path) } } - public ValueTask LoadModuleAsync(string path, CancellationToken cancellationToken) + public ValueTask ReadFileAsync(string path, CancellationToken cancellationToken) { var bytes = File.ReadAllBytes(path); return new(new LuaFile(bytes)); diff --git a/src/Lua/LuaStateExtensions.cs b/src/Lua/LuaStateExtensions.cs index bc24ce61..13279038 100644 --- a/src/Lua/LuaStateExtensions.cs +++ b/src/Lua/LuaStateExtensions.cs @@ -29,7 +29,7 @@ public static async ValueTask LoadFileAsync(this LuaState state, str var name = "@" + fileName; LuaClosure closure; { - using var file = await state.FileManager.LoadModuleAsync(fileName, cancellationToken); + using var file = await state.FileManager.ReadFileAsync(fileName, cancellationToken); closure = file.Type == LuaFileType.Bytes ? state.Load(file.ReadBytes(), name, mode, environment) : state.Load(file.ReadText(), name, environment); From bf1e2d87d87a61de7a8f5d50bf25fd3d6b75909f Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Mon, 19 May 2025 08:47:17 +0900 Subject: [PATCH 04/33] rename: rename LuaFile to LuaFileContent and update related methods --- src/Lua/ILuaFileManager.cs | 6 ++--- src/Lua/{LuaFile.cs => LuaFileContent.cs} | 28 +++++++++++------------ src/Lua/LuaStateExtensions.cs | 4 ++-- 3 files changed, 19 insertions(+), 19 deletions(-) rename src/Lua/{LuaFile.cs => LuaFileContent.cs} (55%) diff --git a/src/Lua/ILuaFileManager.cs b/src/Lua/ILuaFileManager.cs index 31c2aad1..a332c0d7 100644 --- a/src/Lua/ILuaFileManager.cs +++ b/src/Lua/ILuaFileManager.cs @@ -3,7 +3,7 @@ public interface ILuaFileManager { public bool IsReadable(string path); - public ValueTask ReadFileAsync(string path, CancellationToken cancellationToken); + public ValueTask ReadFileContentAsync(string path, CancellationToken cancellationToken); public IStream Open(string path, FileMode mode, FileAccess access); public void Rename (string oldName, string newName); public void Remove (string path); @@ -59,10 +59,10 @@ public bool IsReadable(string path) } } - public ValueTask ReadFileAsync(string path, CancellationToken cancellationToken) + public ValueTask ReadFileContentAsync(string path, CancellationToken cancellationToken) { var bytes = File.ReadAllBytes(path); - return new(new LuaFile(bytes)); + return new(new LuaFileContent(bytes)); } public IStream Open(string path, FileMode mode, FileAccess access) diff --git a/src/Lua/LuaFile.cs b/src/Lua/LuaFileContent.cs similarity index 55% rename from src/Lua/LuaFile.cs rename to src/Lua/LuaFileContent.cs index ffc6317a..049acd77 100644 --- a/src/Lua/LuaFile.cs +++ b/src/Lua/LuaFileContent.cs @@ -2,46 +2,46 @@ namespace Lua; -public enum LuaFileType +public enum LuaFileContentType { Text, Bytes } -public readonly struct LuaFile : IDisposable +public readonly struct LuaFileContent : IDisposable { - public LuaFileType Type => type; + public LuaFileContentType Type => type; - readonly LuaFileType type; + readonly LuaFileContentType type; readonly object referenceValue; - public LuaFile(string text) + public LuaFileContent(string text) { - type = LuaFileType.Text; + type = LuaFileContentType.Text; referenceValue = text; } - public LuaFile(byte[] bytes) + public LuaFileContent(byte[] bytes) { - type = LuaFileType.Bytes; + type = LuaFileContentType.Bytes; referenceValue = bytes; } - public LuaFile(IMemoryOwner bytes) + public LuaFileContent(IMemoryOwner bytes) { - type = LuaFileType.Text; + type = LuaFileContentType.Text; referenceValue = bytes; } - public LuaFile(IMemoryOwner bytes) + public LuaFileContent(IMemoryOwner bytes) { - type = LuaFileType.Bytes; + type = LuaFileContentType.Bytes; referenceValue = bytes; } public ReadOnlySpan ReadText() { - if (type != LuaFileType.Text) throw new Exception(); // TODO: add message + if (type != LuaFileContentType.Text) throw new Exception(); // TODO: add message if (referenceValue is IMemoryOwner mem) { return mem.Memory.Span; @@ -52,7 +52,7 @@ public ReadOnlySpan ReadText() public ReadOnlySpan ReadBytes() { - if (type != LuaFileType.Bytes) throw new Exception(); // TODO: add message + if (type != LuaFileContentType.Bytes) throw new Exception(); // TODO: add message if (referenceValue is IMemoryOwner mem) { return mem.Memory.Span; diff --git a/src/Lua/LuaStateExtensions.cs b/src/Lua/LuaStateExtensions.cs index 13279038..bf45dc8b 100644 --- a/src/Lua/LuaStateExtensions.cs +++ b/src/Lua/LuaStateExtensions.cs @@ -29,8 +29,8 @@ public static async ValueTask LoadFileAsync(this LuaState state, str var name = "@" + fileName; LuaClosure closure; { - using var file = await state.FileManager.ReadFileAsync(fileName, cancellationToken); - closure = file.Type == LuaFileType.Bytes + using var file = await state.FileManager.ReadFileContentAsync(fileName, cancellationToken); + closure = file.Type == LuaFileContentType.Bytes ? state.Load(file.ReadBytes(), name, mode, environment) : state.Load(file.ReadText(), name, environment); } From c784577d84bd91c5efb8d4557fe6ffde4222b898 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Mon, 19 May 2025 20:59:05 +0900 Subject: [PATCH 05/33] rename: rename ILuaFileManager to ILuaFileSystem and update references --- src/Lua/{ILuaFileManager.cs => ILuaFileSystem.cs} | 6 +++--- src/Lua/LuaState.cs | 2 +- src/Lua/LuaStateExtensions.cs | 2 +- src/Lua/Standard/IOLibrary.cs | 4 ++-- src/Lua/Standard/Internal/IOHelper.cs | 2 +- src/Lua/Standard/ModuleLibrary.cs | 2 +- 6 files changed, 9 insertions(+), 9 deletions(-) rename src/Lua/{ILuaFileManager.cs => ILuaFileSystem.cs} (96%) diff --git a/src/Lua/ILuaFileManager.cs b/src/Lua/ILuaFileSystem.cs similarity index 96% rename from src/Lua/ILuaFileManager.cs rename to src/Lua/ILuaFileSystem.cs index a332c0d7..c28235ab 100644 --- a/src/Lua/ILuaFileManager.cs +++ b/src/Lua/ILuaFileSystem.cs @@ -1,6 +1,6 @@ namespace Lua; -public interface ILuaFileManager +public interface ILuaFileSystem { public bool IsReadable(string path); public ValueTask ReadFileContentAsync(string path, CancellationToken cancellationToken); @@ -41,9 +41,9 @@ public interface IStreamWriter : IDisposable public void SetVBuf(string mode, int size); } -public sealed class SystemFileManager : ILuaFileManager +public sealed class FileSystem : ILuaFileSystem { - public static readonly SystemFileManager Instance = new(); + public static readonly FileSystem Instance = new(); public bool IsReadable(string path) { diff --git a/src/Lua/LuaState.cs b/src/Lua/LuaState.cs index 9ee400f4..ea01aca5 100644 --- a/src/Lua/LuaState.cs +++ b/src/Lua/LuaState.cs @@ -38,7 +38,7 @@ public sealed class LuaState public ILuaModuleLoader ModuleLoader { get; set; } = FileModuleLoader.Instance; - public ILuaFileManager FileManager { get; set; } = SystemFileManager.Instance; + public ILuaFileSystem FileSystem { get; set; } = Lua.FileSystem.Instance; // metatables LuaTable? nilMetatable; diff --git a/src/Lua/LuaStateExtensions.cs b/src/Lua/LuaStateExtensions.cs index bf45dc8b..a93286d6 100644 --- a/src/Lua/LuaStateExtensions.cs +++ b/src/Lua/LuaStateExtensions.cs @@ -29,7 +29,7 @@ public static async ValueTask LoadFileAsync(this LuaState state, str var name = "@" + fileName; LuaClosure closure; { - using var file = await state.FileManager.ReadFileContentAsync(fileName, cancellationToken); + using var file = await state.FileSystem.ReadFileContentAsync(fileName, cancellationToken); closure = file.Type == LuaFileContentType.Bytes ? state.Load(file.ReadBytes(), name, mode, environment) : state.Load(file.ReadText(), name, environment); diff --git a/src/Lua/Standard/IOLibrary.cs b/src/Lua/Standard/IOLibrary.cs index e206697a..6cfa9e50 100644 --- a/src/Lua/Standard/IOLibrary.cs +++ b/src/Lua/Standard/IOLibrary.cs @@ -74,7 +74,7 @@ public ValueTask Input(LuaFunctionExecutionContext context, CancellationTok } else { - var stream = context.State.FileManager.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); + var stream = context.State.FileSystem.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); var handle = new FileHandle(stream); registry["stdin"] = new(handle); return new(context.Return(new LuaValue(handle))); @@ -158,7 +158,7 @@ public ValueTask Output(LuaFunctionExecutionContext context, CancellationTo } else { - var stream = context.State.FileManager.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); + var stream = context.State.FileSystem.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); var handle = new FileHandle(stream); io["stdout"] = new(handle); return new(context.Return(new LuaValue(handle))); diff --git a/src/Lua/Standard/Internal/IOHelper.cs b/src/Lua/Standard/Internal/IOHelper.cs index ebe784e9..cb6c58b8 100644 --- a/src/Lua/Standard/Internal/IOHelper.cs +++ b/src/Lua/Standard/Internal/IOHelper.cs @@ -24,7 +24,7 @@ public static int Open(LuaThread thread, string fileName, string mode, bool thro try { - var stream = thread.State.FileManager.Open(fileName, fileMode, fileAccess); + var stream = thread.State.FileSystem.Open(fileName, fileMode, fileAccess); thread.Stack.Push(new LuaValue(new FileHandle(stream))); return 1; } diff --git a/src/Lua/Standard/ModuleLibrary.cs b/src/Lua/Standard/ModuleLibrary.cs index cacc919e..a8262734 100644 --- a/src/Lua/Standard/ModuleLibrary.cs +++ b/src/Lua/Standard/ModuleLibrary.cs @@ -71,7 +71,7 @@ public ValueTask SearchPath(LuaFunctionExecutionContext context, Cancellati { path = pathSpan[..nextIndex].ToString(); var fileName = path.Replace("?", name); - if (state.FileManager.IsReadable(fileName)) + if (state.FileSystem.IsReadable(fileName)) { return fileName; } From e75c81b257708596e99a1735b88e56ebfd5eaeb9 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Mon, 19 May 2025 20:59:25 +0900 Subject: [PATCH 06/33] rename: update FileManager references to FileSystem in OperatingSystemLibrary --- src/Lua/Standard/OperatingSystemLibrary.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Lua/Standard/OperatingSystemLibrary.cs b/src/Lua/Standard/OperatingSystemLibrary.cs index fd2065ca..8bd98af3 100644 --- a/src/Lua/Standard/OperatingSystemLibrary.cs +++ b/src/Lua/Standard/OperatingSystemLibrary.cs @@ -143,7 +143,7 @@ public ValueTask Remove(LuaFunctionExecutionContext context, CancellationTo var fileName = context.GetArgument(0); try { - context.State.FileManager.Remove(fileName); + context.State.FileSystem.Remove(fileName); return new(context.Return(true)); } catch (IOException ex) @@ -158,7 +158,7 @@ public ValueTask Rename(LuaFunctionExecutionContext context, CancellationTo var newName = context.GetArgument(1); try { - context.State.FileManager.Rename(oldName, newName); + context.State.FileSystem.Rename(oldName, newName); return new(context.Return(true)); } catch (IOException ex) From 2403e5bd65c120f7fb22f3267b39466fd3470e0b Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Mon, 19 May 2025 21:10:53 +0900 Subject: [PATCH 07/33] change: move file systems to Lua.IO --- src/Lua/{ => IO}/ILuaFileSystem.cs | 2 +- src/Lua/LuaState.cs | 3 ++- src/Lua/Standard/FileHandle.cs | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) rename src/Lua/{ => IO}/ILuaFileSystem.cs (99%) diff --git a/src/Lua/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs similarity index 99% rename from src/Lua/ILuaFileSystem.cs rename to src/Lua/IO/ILuaFileSystem.cs index c28235ab..7be1642f 100644 --- a/src/Lua/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -1,4 +1,4 @@ -namespace Lua; +namespace Lua.IO; public interface ILuaFileSystem { diff --git a/src/Lua/LuaState.cs b/src/Lua/LuaState.cs index ea01aca5..a7c673ae 100644 --- a/src/Lua/LuaState.cs +++ b/src/Lua/LuaState.cs @@ -2,6 +2,7 @@ using System.Diagnostics.CodeAnalysis; using System.Runtime.CompilerServices; using Lua.Internal; +using Lua.IO; using Lua.Loaders; using Lua.Runtime; using Lua.Standard; @@ -38,7 +39,7 @@ public sealed class LuaState public ILuaModuleLoader ModuleLoader { get; set; } = FileModuleLoader.Instance; - public ILuaFileSystem FileSystem { get; set; } = Lua.FileSystem.Instance; + public ILuaFileSystem FileSystem { get; set; } = Lua.IO.FileSystem.Instance; // metatables LuaTable? nilMetatable; diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index 74dd9c73..6f0fdff0 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -1,3 +1,4 @@ +using Lua.IO; using Lua.Runtime; using Lua.Standard.Internal; From 527898279ba4921cba9b33ccc7f65127dff568a5 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Mon, 19 May 2025 21:16:12 +0900 Subject: [PATCH 08/33] refactor: rename SystemStream references to StreamWrapper --- src/Lua/IO/ILuaFileSystem.cs | 30 +++++++++--------------------- src/Lua/Standard/FileHandle.cs | 2 +- 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index 7be1642f..3276ad95 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -5,11 +5,10 @@ public interface ILuaFileSystem public bool IsReadable(string path); public ValueTask ReadFileContentAsync(string path, CancellationToken cancellationToken); public IStream Open(string path, FileMode mode, FileAccess access); - public void Rename (string oldName, string newName); - public void Remove (string path); + public void Rename(string oldName, string newName); + public void Remove(string path); } - public interface IStream : IDisposable { public IStreamReader? Reader { get; } @@ -67,28 +66,23 @@ public ValueTask ReadFileContentAsync(string path, CancellationT public IStream Open(string path, FileMode mode, FileAccess access) { - return new SystemStream(File.Open(path, mode, access)); + return new StreamWrapper(File.Open(path, mode, access)); } - public ValueTask WriteAllTextAsync(string path, string text, CancellationToken cancellationToken) - { - File.WriteAllText(path, text); - return new(); - } 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); } } -public sealed class SystemStreamReader(StreamReader streamReader) : IStreamReader +public sealed class StreamReaderWrapper(StreamReader streamReader) : IStreamReader { public ValueTask ReadLineAsync(CancellationToken cancellationToken) { @@ -111,7 +105,7 @@ public void Dispose() } } -public sealed class SystemStreamWriter(StreamWriter streamWriter) : IStreamWriter +public sealed class StreamWriterWrapper(StreamWriter streamWriter) : IStreamWriter { public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) { @@ -119,12 +113,6 @@ public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cance return new(); } - public ValueTask WriteLineAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) - { - streamWriter.WriteLine(buffer.Span); - return new(); - } - public ValueTask FlushAsync(CancellationToken cancellationToken) { streamWriter.Flush(); @@ -143,10 +131,10 @@ public void Dispose() } } -public sealed class SystemStream(Stream fileStream) : IStream +public sealed class StreamWrapper(Stream fileStream) : IStream { - public IStreamReader? Reader => fileStream.CanRead ? new SystemStreamReader(new(fileStream)) : null; - public IStreamWriter? Writer => fileStream.CanWrite ? new SystemStreamWriter(new(fileStream)) : null; + public IStreamReader? Reader => fileStream.CanRead ? new StreamReaderWrapper(new(fileStream)) : null; + public IStreamWriter? Writer => fileStream.CanWrite ? new StreamWriterWrapper(new(fileStream)) : null; public long Seek(long offset, SeekOrigin origin) => fileStream.Seek(offset, origin); diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index 6f0fdff0..4f8bcc5b 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -50,7 +50,7 @@ static FileHandle() fileHandleMetatable[Metamethods.Index] = IndexMetamethod; } - public FileHandle(Stream stream) : this(new SystemStream(stream)) { } + public FileHandle(Stream stream) : this(new StreamWrapper(stream)) { } public FileHandle(IStream stream) { From 95e1c80b1f6d9499778f6cbe22949e06d5dd6129 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Tue, 20 May 2025 18:33:22 +0900 Subject: [PATCH 09/33] refactor: update ReadByteAsync to ReadStringAsync and simplify Seek method --- src/Lua/IO/ILuaFileSystem.cs | 18 ++++++++--- src/Lua/Runtime/LuaThreadAccessExtensions.cs | 2 +- src/Lua/Standard/FileHandle.cs | 32 ++++++-------------- src/Lua/Standard/Internal/IOHelper.cs | 23 ++++++-------- 4 files changed, 33 insertions(+), 42 deletions(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index 3276ad95..ae30b0e9 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -1,4 +1,6 @@ -namespace Lua.IO; +using Lua.Internal; + +namespace Lua.IO; public interface ILuaFileSystem { @@ -29,7 +31,7 @@ public interface IStreamReader : IDisposable { public ValueTask ReadLineAsync(CancellationToken cancellationToken); public ValueTask ReadToEndAsync(CancellationToken cancellationToken); - public ValueTask ReadByteAsync(CancellationToken cancellationToken); + public ValueTask ReadStringAsync(int count,CancellationToken cancellationToken); } public interface IStreamWriter : IDisposable @@ -94,9 +96,17 @@ public ValueTask ReadToEndAsync(CancellationToken cancellationToken) return new(streamReader.ReadToEnd()); } - public ValueTask ReadByteAsync(CancellationToken cancellationToken) + public ValueTask ReadStringAsync(int count,CancellationToken cancellationToken) { - return new(streamReader.Read()); + using var byteBuffer = new PooledArray(count); + var span = byteBuffer.AsSpan(); + var ret=streamReader.Read(span); + if(ret != span.Length) + { + return new(default(string)); + } + + return new(span.ToString()); } public void Dispose() diff --git a/src/Lua/Runtime/LuaThreadAccessExtensions.cs b/src/Lua/Runtime/LuaThreadAccessExtensions.cs index 2e6f7168..65818a73 100644 --- a/src/Lua/Runtime/LuaThreadAccessExtensions.cs +++ b/src/Lua/Runtime/LuaThreadAccessExtensions.cs @@ -183,7 +183,7 @@ public static async ValueTask GetTable(this LuaThreadAccess access, Lu { if (luaTable.TryGetValue(key, out var value)) { - return new(value); + return (value); } } diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index 4f8bcc5b..374ad383 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -69,9 +69,9 @@ public ValueTask ReadToEndAsync(CancellationToken cancellationToken) return reader!.ReadToEndAsync(cancellationToken); } - public ValueTask ReadByteAsync(CancellationToken cancellationToken) + public ValueTask ReadStringAsync(int count,CancellationToken cancellationToken) { - return reader!.ReadByteAsync(cancellationToken); + return reader!.ReadStringAsync(count,cancellationToken); } public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) @@ -79,28 +79,14 @@ public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cance return writer!.WriteAsync(buffer, cancellationToken); } - public long Seek(string whence, long offset) - { - if (whence != null) + public long Seek(string whence, long offset) => + whence switch { - switch (whence) - { - case "set": - stream.Seek(offset, SeekOrigin.Begin); - break; - case "cur": - stream.Seek(offset, SeekOrigin.Current); - break; - case "end": - stream.Seek(offset, SeekOrigin.End); - break; - default: - throw new ArgumentException($"Invalid option '{whence}'"); - } - } - - return stream.Position; - } + "set" => stream.Seek(offset, SeekOrigin.Begin), + "cur" => stream.Seek(offset, SeekOrigin.Current), + "end" => stream.Seek(offset, SeekOrigin.End), + _ => throw new ArgumentException($"Invalid option '{whence}'") + }; public ValueTask FlushAsync(CancellationToken cancellationToken) { diff --git a/src/Lua/Standard/Internal/IOHelper.cs b/src/Lua/Standard/Internal/IOHelper.cs index cb6c58b8..b3c89c31 100644 --- a/src/Lua/Standard/Internal/IOHelper.cs +++ b/src/Lua/Standard/Internal/IOHelper.cs @@ -125,22 +125,17 @@ public static async ValueTask ReadAsync(LuaThread thread, FileHandle file, } else if (format.TryRead(out var count)) { - using var byteBuffer = new PooledArray(count); - - for (int j = 0; j < count; j++) + var ret = await file.ReadStringAsync(count, cancellationToken); + if (ret == null) { - var b = await file.ReadByteAsync(cancellationToken); - if (b == -1) - { - stack.PopUntil(top); - stack.Push(LuaValue.Nil); - return 1; - } - - byteBuffer[j] = (byte)b; + stack.PopUntil(top); + stack.Push(default); + return 1; + } + else + { + stack.Push(ret); } - - stack.Push(Encoding.UTF8.GetString(byteBuffer.AsSpan())); } else { From f25d1aa670f6f31c89347c177b436f37ed9fc6de Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Tue, 20 May 2025 19:20:44 +0900 Subject: [PATCH 10/33] refactor: simplify FileHandle by removing reader/writer and using IStream directly --- src/Lua/IO/ILuaFileSystem.cs | 79 ++++++++++++++++------------------ src/Lua/Standard/FileHandle.cs | 36 +++++----------- 2 files changed, 48 insertions(+), 67 deletions(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index ae30b0e9..4b7bea54 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -13,8 +13,13 @@ public interface ILuaFileSystem public interface IStream : IDisposable { - public IStreamReader? Reader { get; } - public IStreamWriter? Writer { get; } + public ValueTask ReadLineAsync(CancellationToken cancellationToken); + public ValueTask ReadToEndAsync(CancellationToken cancellationToken); + public ValueTask ReadStringAsync(int count, CancellationToken cancellationToken); + + public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken); + public ValueTask FlushAsync(CancellationToken cancellationToken); + public void SetVBuf(string mode, int size); public long Seek(long offset, SeekOrigin origin); @@ -29,9 +34,6 @@ public interface IStream : IDisposable public interface IStreamReader : IDisposable { - public ValueTask ReadLineAsync(CancellationToken cancellationToken); - public ValueTask ReadToEndAsync(CancellationToken cancellationToken); - public ValueTask ReadStringAsync(int count,CancellationToken cancellationToken); } public interface IStreamWriter : IDisposable @@ -84,24 +86,33 @@ public void Remove(string path) } } -public sealed class StreamReaderWrapper(StreamReader streamReader) : IStreamReader +public sealed class StreamWrapper(Stream innerStream) : IStream { + StreamReader? reader; + StreamWriter? writer; + public ValueTask ReadLineAsync(CancellationToken cancellationToken) { - return new(streamReader.ReadLine()); + reader ??= new(innerStream); + + return new(reader.ReadLine()); } public ValueTask ReadToEndAsync(CancellationToken cancellationToken) { - return new(streamReader.ReadToEnd()); + reader ??= new(innerStream); + + return new(reader.ReadToEnd()); } - public ValueTask ReadStringAsync(int count,CancellationToken cancellationToken) + public ValueTask ReadStringAsync(int count, CancellationToken cancellationToken) { + reader ??= new(innerStream); + using var byteBuffer = new PooledArray(count); var span = byteBuffer.AsSpan(); - var ret=streamReader.Read(span); - if(ret != span.Length) + var ret = reader.Read(span); + if (ret != span.Length) { return new(default(string)); } @@ -109,57 +120,41 @@ public ValueTask ReadToEndAsync(CancellationToken cancellationToken) return new(span.ToString()); } - public void Dispose() - { - streamReader.Dispose(); - } -} - -public sealed class StreamWriterWrapper(StreamWriter streamWriter) : IStreamWriter -{ public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) { - streamWriter.Write(buffer.Span); + writer ??= new(innerStream); + + writer.Write(buffer.Span); return new(); } public ValueTask FlushAsync(CancellationToken cancellationToken) { - streamWriter.Flush(); + innerStream.Flush(); return new(); } public void SetVBuf(string mode, int size) { + writer ??= new(innerStream); // Ignore size parameter - streamWriter.AutoFlush = mode is "no" or "line"; + writer.AutoFlush = mode is "no" or "line"; } - public void Dispose() - { - streamWriter.Dispose(); - } -} - -public sealed class StreamWrapper(Stream fileStream) : IStream -{ - public IStreamReader? Reader => fileStream.CanRead ? new StreamReaderWrapper(new(fileStream)) : null; - public IStreamWriter? Writer => fileStream.CanWrite ? new StreamWriterWrapper(new(fileStream)) : null; - - public long Seek(long offset, SeekOrigin origin) => fileStream.Seek(offset, origin); + public long Seek(long offset, SeekOrigin origin) => innerStream.Seek(offset, origin); - public void SetLength(long value) => fileStream.SetLength(value); + public void SetLength(long value) => innerStream.SetLength(value); - public bool CanRead => fileStream.CanRead; - public bool CanSeek => fileStream.CanSeek; - public bool CanWrite => fileStream.CanWrite; - public long Length => fileStream.Length; + public bool CanRead => innerStream.CanRead; + public bool CanSeek => innerStream.CanSeek; + public bool CanWrite => innerStream.CanWrite; + public long Length => innerStream.Length; public long Position { - get => fileStream.Position; - set => fileStream.Position = value; + get => innerStream.Position; + set => innerStream.Position = value; } - public void Dispose() => fileStream.Dispose(); + public void Dispose() => innerStream.Dispose(); } \ No newline at end of file diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index 374ad383..e8e662f2 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -17,7 +17,7 @@ public class FileHandle : ILuaUserData { return new(context.Return(name switch { - "close" => CloseFunction!, + "close" => CloseFunction, "flush" => FlushFunction!, "lines" => LinesFunction!, "read" => ReadFunction!, @@ -34,8 +34,6 @@ public class FileHandle : ILuaUserData }); IStream stream; - IStreamWriter? writer; - IStreamReader? reader; bool isClosed; public bool IsClosed => Volatile.Read(ref isClosed); @@ -46,7 +44,7 @@ public class FileHandle : ILuaUserData static FileHandle() { - fileHandleMetatable = new LuaTable(); + fileHandleMetatable = new LuaTable(0,1); fileHandleMetatable[Metamethods.Index] = IndexMetamethod; } @@ -55,28 +53,26 @@ public FileHandle(Stream stream) : this(new StreamWrapper(stream)) { } public FileHandle(IStream stream) { this.stream = stream; - if (stream.CanRead) reader = stream.Reader; - if (stream.CanWrite) writer = stream.Writer; } public ValueTask ReadLineAsync(CancellationToken cancellationToken) { - return reader!.ReadLineAsync(cancellationToken); + return stream.ReadLineAsync(cancellationToken); } public ValueTask ReadToEndAsync(CancellationToken cancellationToken) { - return reader!.ReadToEndAsync(cancellationToken); + return stream.ReadToEndAsync(cancellationToken); } public ValueTask ReadStringAsync(int count,CancellationToken cancellationToken) { - return reader!.ReadStringAsync(count,cancellationToken); + return stream.ReadStringAsync(count,cancellationToken); } public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) { - return writer!.WriteAsync(buffer, cancellationToken); + return stream.WriteAsync(buffer, cancellationToken); } public long Seek(string whence, long offset) => @@ -90,12 +86,12 @@ public long Seek(string whence, long offset) => public ValueTask FlushAsync(CancellationToken cancellationToken) { - return writer!.FlushAsync(cancellationToken); + return stream.FlushAsync(cancellationToken); } public void SetVBuf(string mode, int size) { - writer!.SetVBuf(mode, size); + stream.SetVBuf(mode, size); } public void Close() @@ -103,22 +99,12 @@ public void Close() if (isClosed) throw new ObjectDisposedException(nameof(FileHandle)); Volatile.Write(ref isClosed, true); - if (reader != null) - { - reader.Dispose(); - } - else if (writer != null) - { - writer.Dispose(); - } - else - { + + stream.Dispose(); - } + stream = null!; - writer = null; - reader = null; } static readonly LuaFunction CloseFunction = new("close", (context, cancellationToken) => From 3ef61e3ae66e82bf121089fc3a111ac424249592 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Tue, 20 May 2025 20:52:20 +0900 Subject: [PATCH 11/33] refactor: update file opening methods to use LuaFileMode and simplify access handling --- src/Lua/IO/ILuaFileSystem.cs | 19 +++++++++++++-- src/Lua/IO/LuaFileMode.cs | 35 +++++++++++++++++++++++++++ src/Lua/Standard/FileHandle.cs | 2 +- src/Lua/Standard/IOLibrary.cs | 5 ++-- src/Lua/Standard/Internal/IOHelper.cs | 20 +++++++-------- 5 files changed, 66 insertions(+), 15 deletions(-) create mode 100644 src/Lua/IO/LuaFileMode.cs diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index 4b7bea54..3536bbdd 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -6,7 +6,7 @@ public interface ILuaFileSystem { public bool IsReadable(string path); public ValueTask ReadFileContentAsync(string path, CancellationToken cancellationToken); - public IStream Open(string path, FileMode mode, FileAccess access); + public IStream Open(string path, LuaFileMode mode); public void Rename(string oldName, string newName); public void Remove(string path); } @@ -48,6 +48,20 @@ public sealed class FileSystem : ILuaFileSystem { public static readonly FileSystem Instance = new(); + public static (FileMode, FileAccess access) GetFileMode(LuaFileMode luaFileMode) + { + return luaFileMode switch + { + LuaFileMode.Read => (FileMode.Open, FileAccess.Read), + LuaFileMode.Write => (FileMode.Create, FileAccess.Write), + LuaFileMode.Append => (FileMode.Append, FileAccess.Write), + LuaFileMode.ReadWriteOpen => (FileMode.Open, FileAccess.ReadWrite), + LuaFileMode.ReadWriteCreate => (FileMode.Create, FileAccess.ReadWrite), + LuaFileMode.ReadAppend => (FileMode.Append, FileAccess.ReadWrite), + _ => throw new ArgumentOutOfRangeException(nameof(luaFileMode), luaFileMode, null) + }; + } + public bool IsReadable(string path) { if (!File.Exists(path)) return false; @@ -68,8 +82,9 @@ public ValueTask ReadFileContentAsync(string path, CancellationT return new(new LuaFileContent(bytes)); } - public IStream Open(string path, FileMode mode, FileAccess access) + public IStream Open(string path, LuaFileMode luaMode) { + var (mode, access) = GetFileMode(luaMode); return new StreamWrapper(File.Open(path, mode, access)); } diff --git a/src/Lua/IO/LuaFileMode.cs b/src/Lua/IO/LuaFileMode.cs new file mode 100644 index 00000000..ae2a2ef2 --- /dev/null +++ b/src/Lua/IO/LuaFileMode.cs @@ -0,0 +1,35 @@ +namespace Lua.IO +{ + public enum LuaFileMode + { + /// + /// r + /// + Read, + + /// + /// w + /// + Write, + + /// + /// a + /// + Append, + + /// + /// r+ + /// + ReadWriteOpen, + + /// + /// w+ + /// + ReadWriteCreate, + + /// + /// a+ + /// + ReadAppend, + } +} \ No newline at end of file diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index e8e662f2..bca86ebd 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -17,7 +17,7 @@ public class FileHandle : ILuaUserData { return new(context.Return(name switch { - "close" => CloseFunction, + "close" => CloseFunction!, "flush" => FlushFunction!, "lines" => LinesFunction!, "read" => ReadFunction!, diff --git a/src/Lua/Standard/IOLibrary.cs b/src/Lua/Standard/IOLibrary.cs index 6cfa9e50..5f1a8998 100644 --- a/src/Lua/Standard/IOLibrary.cs +++ b/src/Lua/Standard/IOLibrary.cs @@ -1,3 +1,4 @@ +using Lua.IO; using Lua.Runtime; using Lua.Standard.Internal; @@ -74,7 +75,7 @@ public ValueTask Input(LuaFunctionExecutionContext context, CancellationTok } else { - var stream = context.State.FileSystem.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); + var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileMode.ReadWriteOpen); var handle = new FileHandle(stream); registry["stdin"] = new(handle); return new(context.Return(new LuaValue(handle))); @@ -158,7 +159,7 @@ public ValueTask Output(LuaFunctionExecutionContext context, CancellationTo } else { - var stream = context.State.FileSystem.Open(arg.ToString()!, FileMode.Open, FileAccess.ReadWrite); + var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileMode.ReadWriteOpen); var handle = new FileHandle(stream); io["stdout"] = new(handle); return new(context.Return(new LuaValue(handle))); diff --git a/src/Lua/Standard/Internal/IOHelper.cs b/src/Lua/Standard/Internal/IOHelper.cs index b3c89c31..e6c42e2c 100644 --- a/src/Lua/Standard/Internal/IOHelper.cs +++ b/src/Lua/Standard/Internal/IOHelper.cs @@ -1,5 +1,6 @@ using System.Text; using Lua.Internal; +using Lua.IO; namespace Lua.Standard.Internal; @@ -9,22 +10,21 @@ public static int Open(LuaThread thread, string fileName, string mode, bool thro { 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, + "r" or "rb" => LuaFileMode.Read, + "w" or "wb" => LuaFileMode.Write, + "a" or "ab" => LuaFileMode.Append, + "r+" or "rb+" => LuaFileMode.ReadWriteOpen, + "w+" or "wb+" => LuaFileMode.ReadWriteCreate, + "a+" or "ab+" => LuaFileMode.ReadAppend, _ => throw new LuaRuntimeException(thread, "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, - }; + var binary = mode.Contains("b"); + if (binary) throw new LuaRuntimeException(thread, "binary mode is not supported"); try { - var stream = thread.State.FileSystem.Open(fileName, fileMode, fileAccess); + var stream = thread.State.FileSystem.Open(fileName, fileMode); thread.Stack.Push(new LuaValue(new FileHandle(stream))); return 1; } From fa2c90d27298959d31271ad65712cf6f977b3dfb Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Tue, 20 May 2025 20:53:59 +0900 Subject: [PATCH 12/33] refactor: rename LuaFileMode to LuaFileOpenMode --- src/Lua/IO/ILuaFileSystem.cs | 22 +++++++++---------- .../IO/{LuaFileMode.cs => LuaFileOpenMode.cs} | 2 +- src/Lua/Standard/IOLibrary.cs | 4 ++-- src/Lua/Standard/Internal/IOHelper.cs | 12 +++++----- 4 files changed, 20 insertions(+), 20 deletions(-) rename src/Lua/IO/{LuaFileMode.cs => LuaFileOpenMode.cs} (94%) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index 3536bbdd..cf3bd661 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -6,7 +6,7 @@ public interface ILuaFileSystem { public bool IsReadable(string path); public ValueTask ReadFileContentAsync(string path, CancellationToken cancellationToken); - public IStream Open(string path, LuaFileMode mode); + public IStream Open(string path, LuaFileOpenMode mode); public void Rename(string oldName, string newName); public void Remove(string path); } @@ -48,17 +48,17 @@ public sealed class FileSystem : ILuaFileSystem { public static readonly FileSystem Instance = new(); - public static (FileMode, FileAccess access) GetFileMode(LuaFileMode luaFileMode) + public static (FileMode, FileAccess access) GetFileMode(LuaFileOpenMode luaFileOpenMode) { - return luaFileMode switch + return luaFileOpenMode switch { - LuaFileMode.Read => (FileMode.Open, FileAccess.Read), - LuaFileMode.Write => (FileMode.Create, FileAccess.Write), - LuaFileMode.Append => (FileMode.Append, FileAccess.Write), - LuaFileMode.ReadWriteOpen => (FileMode.Open, FileAccess.ReadWrite), - LuaFileMode.ReadWriteCreate => (FileMode.Create, FileAccess.ReadWrite), - LuaFileMode.ReadAppend => (FileMode.Append, FileAccess.ReadWrite), - _ => throw new ArgumentOutOfRangeException(nameof(luaFileMode), luaFileMode, null) + 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.Create, FileAccess.ReadWrite), + LuaFileOpenMode.ReadAppend => (FileMode.Append, FileAccess.ReadWrite), + _ => throw new ArgumentOutOfRangeException(nameof(luaFileOpenMode), luaFileOpenMode, null) }; } @@ -82,7 +82,7 @@ public ValueTask ReadFileContentAsync(string path, CancellationT return new(new LuaFileContent(bytes)); } - public IStream Open(string path, LuaFileMode luaMode) + public IStream Open(string path, LuaFileOpenMode luaMode) { var (mode, access) = GetFileMode(luaMode); return new StreamWrapper(File.Open(path, mode, access)); diff --git a/src/Lua/IO/LuaFileMode.cs b/src/Lua/IO/LuaFileOpenMode.cs similarity index 94% rename from src/Lua/IO/LuaFileMode.cs rename to src/Lua/IO/LuaFileOpenMode.cs index ae2a2ef2..719c0c88 100644 --- a/src/Lua/IO/LuaFileMode.cs +++ b/src/Lua/IO/LuaFileOpenMode.cs @@ -1,6 +1,6 @@ namespace Lua.IO { - public enum LuaFileMode + public enum LuaFileOpenMode { /// /// r diff --git a/src/Lua/Standard/IOLibrary.cs b/src/Lua/Standard/IOLibrary.cs index 5f1a8998..3482f786 100644 --- a/src/Lua/Standard/IOLibrary.cs +++ b/src/Lua/Standard/IOLibrary.cs @@ -75,7 +75,7 @@ public ValueTask Input(LuaFunctionExecutionContext context, CancellationTok } else { - var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileMode.ReadWriteOpen); + var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen); var handle = new FileHandle(stream); registry["stdin"] = new(handle); return new(context.Return(new LuaValue(handle))); @@ -159,7 +159,7 @@ public ValueTask Output(LuaFunctionExecutionContext context, CancellationTo } else { - var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileMode.ReadWriteOpen); + var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen); var handle = new FileHandle(stream); io["stdout"] = new(handle); return new(context.Return(new LuaValue(handle))); diff --git a/src/Lua/Standard/Internal/IOHelper.cs b/src/Lua/Standard/Internal/IOHelper.cs index e6c42e2c..341a2490 100644 --- a/src/Lua/Standard/Internal/IOHelper.cs +++ b/src/Lua/Standard/Internal/IOHelper.cs @@ -10,12 +10,12 @@ public static int Open(LuaThread thread, string fileName, string mode, bool thro { var fileMode = mode switch { - "r" or "rb" => LuaFileMode.Read, - "w" or "wb" => LuaFileMode.Write, - "a" or "ab" => LuaFileMode.Append, - "r+" or "rb+" => LuaFileMode.ReadWriteOpen, - "w+" or "wb+" => LuaFileMode.ReadWriteCreate, - "a+" or "ab+" => LuaFileMode.ReadAppend, + "r" or "rb" => LuaFileOpenMode.Read, + "w" or "wb" => LuaFileOpenMode.Write, + "a" or "ab" => LuaFileOpenMode.Append, + "r+" or "rb+" => LuaFileOpenMode.ReadWriteOpen, + "w+" or "wb+" => LuaFileOpenMode.ReadWriteCreate, + "a+" or "ab+" => LuaFileOpenMode.ReadAppend, _ => throw new LuaRuntimeException(thread, "bad argument #2 to 'open' (invalid mode)"), }; From 12413f36dc9681cca53501da5d3681c988e5055f Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Tue, 20 May 2025 21:07:13 +0900 Subject: [PATCH 13/33] change: update Open method to include throwError parameter for error handling --- src/Lua/IO/ILuaFileSystem.cs | 18 +++++++++++++++--- src/Lua/Standard/IOLibrary.cs | 4 ++-- src/Lua/Standard/Internal/IOHelper.cs | 7 ++++++- 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index cf3bd661..edd5a174 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -6,7 +6,7 @@ public interface ILuaFileSystem { public bool IsReadable(string path); public ValueTask ReadFileContentAsync(string path, CancellationToken cancellationToken); - public IStream Open(string path, LuaFileOpenMode mode); + public IStream? Open(string path, LuaFileOpenMode mode, bool throwError); public void Rename(string oldName, string newName); public void Remove(string path); } @@ -82,10 +82,22 @@ public ValueTask ReadFileContentAsync(string path, CancellationT return new(new LuaFileContent(bytes)); } - public IStream Open(string path, LuaFileOpenMode luaMode) + public IStream? Open(string path, LuaFileOpenMode luaMode, bool throwError) { var (mode, access) = GetFileMode(luaMode); - return new StreamWrapper(File.Open(path, mode, access)); + try + { + return new StreamWrapper(File.Open(path, mode, access)); + } + catch (Exception) + { + if (throwError) + { + throw; + } + + return null; + } } public void Rename(string oldName, string newName) diff --git a/src/Lua/Standard/IOLibrary.cs b/src/Lua/Standard/IOLibrary.cs index 3482f786..1e8e695e 100644 --- a/src/Lua/Standard/IOLibrary.cs +++ b/src/Lua/Standard/IOLibrary.cs @@ -75,7 +75,7 @@ public ValueTask Input(LuaFunctionExecutionContext context, CancellationTok } else { - var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen); + var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen,true)!; var handle = new FileHandle(stream); registry["stdin"] = new(handle); return new(context.Return(new LuaValue(handle))); @@ -159,7 +159,7 @@ public ValueTask Output(LuaFunctionExecutionContext context, CancellationTo } else { - var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen); + var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen,true)!; var handle = new FileHandle(stream); io["stdout"] = new(handle); return new(context.Return(new LuaValue(handle))); diff --git a/src/Lua/Standard/Internal/IOHelper.cs b/src/Lua/Standard/Internal/IOHelper.cs index 341a2490..6c89ef78 100644 --- a/src/Lua/Standard/Internal/IOHelper.cs +++ b/src/Lua/Standard/Internal/IOHelper.cs @@ -24,7 +24,12 @@ public static int Open(LuaThread thread, string fileName, string mode, bool thro try { - var stream = thread.State.FileSystem.Open(fileName, fileMode); + var stream = thread.State.FileSystem.Open(fileName, fileMode, throwError); + if (stream == null) + { + return 0; + } + thread.Stack.Push(new LuaValue(new FileHandle(stream))); return 1; } From 0cf2eca99a24802cdf1fe099ae9461deb681b14c Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Tue, 20 May 2025 21:09:47 +0900 Subject: [PATCH 14/33] feat: add GetTempFileName method to ILuaFileSystem and update TmpName to use it --- src/Lua/IO/ILuaFileSystem.cs | 6 ++++++ src/Lua/Standard/OperatingSystemLibrary.cs | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index edd5a174..9010367b 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -9,6 +9,7 @@ public interface ILuaFileSystem public IStream? Open(string path, LuaFileOpenMode mode, bool throwError); public void Rename(string oldName, string newName); public void Remove(string path); + public string GetTempFileName (); } public interface IStream : IDisposable @@ -111,6 +112,11 @@ public void Remove(string path) { File.Delete(path); } + + public string GetTempFileName () + { + return Path.GetTempFileName(); + } } public sealed class StreamWrapper(Stream innerStream) : IStream diff --git a/src/Lua/Standard/OperatingSystemLibrary.cs b/src/Lua/Standard/OperatingSystemLibrary.cs index 8bd98af3..00985e54 100644 --- a/src/Lua/Standard/OperatingSystemLibrary.cs +++ b/src/Lua/Standard/OperatingSystemLibrary.cs @@ -190,6 +190,6 @@ public ValueTask Time(LuaFunctionExecutionContext context, CancellationToke public ValueTask TmpName(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { - return new(context.Return(Path.GetTempFileName())); + return new(context.Return(context.State.FileSystem.GetTempFileName())); } } \ No newline at end of file From 89d6ce76fa9f20ae543addbc26aa3692be9a97c5 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Tue, 20 May 2025 22:02:43 +0900 Subject: [PATCH 15/33] reformat --- src/Lua/IO/ILuaFileSystem.cs | 6 +++--- src/Lua/Standard/FileHandle.cs | 13 ++++--------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index 9010367b..24ba65c0 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -9,7 +9,7 @@ public interface ILuaFileSystem public IStream? Open(string path, LuaFileOpenMode mode, bool throwError); public void Rename(string oldName, string newName); public void Remove(string path); - public string GetTempFileName (); + public string GetTempFileName(); } public interface IStream : IDisposable @@ -112,8 +112,8 @@ public void Remove(string path) { File.Delete(path); } - - public string GetTempFileName () + + public string GetTempFileName() { return Path.GetTempFileName(); } diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index bca86ebd..b859dcb6 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -44,7 +44,7 @@ public class FileHandle : ILuaUserData static FileHandle() { - fileHandleMetatable = new LuaTable(0,1); + fileHandleMetatable = new LuaTable(0, 1); fileHandleMetatable[Metamethods.Index] = IndexMetamethod; } @@ -65,9 +65,9 @@ public ValueTask ReadToEndAsync(CancellationToken cancellationToken) return stream.ReadToEndAsync(cancellationToken); } - public ValueTask ReadStringAsync(int count,CancellationToken cancellationToken) + public ValueTask ReadStringAsync(int count, CancellationToken cancellationToken) { - return stream.ReadStringAsync(count,cancellationToken); + return stream.ReadStringAsync(count, cancellationToken); } public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) @@ -98,12 +98,7 @@ public void Close() { if (isClosed) throw new ObjectDisposedException(nameof(FileHandle)); Volatile.Write(ref isClosed, true); - - - - stream.Dispose(); - - + stream.Dispose(); stream = null!; } From 060ee0fc6a9a06fc533fd711e3b2ff2c2f016f33 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Tue, 20 May 2025 23:51:14 +0900 Subject: [PATCH 16/33] feat: add DirectorySeparator property to ILuaFileSystem --- src/Lua/IO/ILuaFileSystem.cs | 16 ++++------------ src/Lua/Standard/ModuleLibrary.cs | 2 +- 2 files changed, 5 insertions(+), 13 deletions(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index 24ba65c0..69383587 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -9,6 +9,7 @@ public interface ILuaFileSystem public IStream? Open(string path, LuaFileOpenMode mode, bool throwError); public void Rename(string oldName, string newName); public void Remove(string path); + public string DirectorySeparator { get; } public string GetTempFileName(); } @@ -33,18 +34,6 @@ public interface IStream : IDisposable public long Position { get; set; } } -public interface IStreamReader : IDisposable -{ -} - -public interface IStreamWriter : IDisposable -{ - public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken); - public ValueTask FlushAsync(CancellationToken cancellationToken); - - public void SetVBuf(string mode, int size); -} - public sealed class FileSystem : ILuaFileSystem { public static readonly FileSystem Instance = new(); @@ -113,6 +102,9 @@ 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(); diff --git a/src/Lua/Standard/ModuleLibrary.cs b/src/Lua/Standard/ModuleLibrary.cs index a8262734..816a1583 100644 --- a/src/Lua/Standard/ModuleLibrary.cs +++ b/src/Lua/Standard/ModuleLibrary.cs @@ -130,7 +130,7 @@ public ValueTask SearcherPreload(LuaFunctionExecutionContext context, Cance public async ValueTask SearcherLua(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var name = context.GetArgument(0); - var fileName = await FindFile(context.Access, name, "path", Path.DirectorySeparatorChar.ToString()); + var fileName = await FindFile(context.Access, name, "path", context.State.FileSystem.DirectorySeparator); if (fileName == null) { return (context.Return(LuaValue.Nil)); From 5199f2f83b7e169a92ad3d2104cabdf1524c8c34 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Wed, 21 May 2025 00:13:13 +0900 Subject: [PATCH 17/33] fix: FileHandle.Read --- src/Lua/Standard/FileHandle.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index b859dcb6..40d5ff2a 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -153,8 +153,9 @@ public void Close() static readonly LuaFunction ReadFunction = new("read", async (context, cancellationToken) => { var file = context.GetArgument(0); + var args = context.Arguments[1..].ToArray(); context.Return(); - var resultCount = await IOHelper.ReadAsync(context.Thread, file, "read", 1, context.Arguments[1..].ToArray(), false, cancellationToken); + var resultCount = await IOHelper.ReadAsync(context.Thread, file, "read", 1, args, false, cancellationToken); return resultCount; }); From 299ab4d3fb32fdacd480fe15db24b15bba56ead7 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Wed, 21 May 2025 00:23:00 +0900 Subject: [PATCH 18/33] rename: rename IStream to ILuaIOStream --- src/Lua/IO/ILuaFileSystem.cs | 10 +++++----- src/Lua/Standard/FileHandle.cs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index 69383587..bea72b88 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -6,14 +6,14 @@ public interface ILuaFileSystem { public bool IsReadable(string path); public ValueTask ReadFileContentAsync(string path, CancellationToken cancellationToken); - public IStream? Open(string path, LuaFileOpenMode mode, bool throwError); + public ILuaIOStream? Open(string path, LuaFileOpenMode mode, bool throwError); public void Rename(string oldName, string newName); public void Remove(string path); public string DirectorySeparator { get; } public string GetTempFileName(); } -public interface IStream : IDisposable +public interface ILuaIOStream : IDisposable { public ValueTask ReadLineAsync(CancellationToken cancellationToken); public ValueTask ReadToEndAsync(CancellationToken cancellationToken); @@ -72,12 +72,12 @@ public ValueTask ReadFileContentAsync(string path, CancellationT return new(new LuaFileContent(bytes)); } - public IStream? Open(string path, LuaFileOpenMode luaMode, bool throwError) + public ILuaIOStream? Open(string path, LuaFileOpenMode luaMode, bool throwError) { var (mode, access) = GetFileMode(luaMode); try { - return new StreamWrapper(File.Open(path, mode, access)); + return new LuaIOStreamWrapper(File.Open(path, mode, access)); } catch (Exception) { @@ -111,7 +111,7 @@ public string GetTempFileName() } } -public sealed class StreamWrapper(Stream innerStream) : IStream +public sealed class LuaIOStreamWrapper(Stream innerStream) : ILuaIOStream { StreamReader? reader; StreamWriter? writer; diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index 40d5ff2a..93f93a14 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -33,7 +33,7 @@ public class FileHandle : ILuaUserData } }); - IStream stream; + ILuaIOStream stream; bool isClosed; public bool IsClosed => Volatile.Read(ref isClosed); @@ -48,9 +48,9 @@ static FileHandle() fileHandleMetatable[Metamethods.Index] = IndexMetamethod; } - public FileHandle(Stream stream) : this(new StreamWrapper(stream)) { } + public FileHandle(Stream stream) : this(new LuaIOStreamWrapper(stream)) { } - public FileHandle(IStream stream) + public FileHandle(ILuaIOStream stream) { this.stream = stream; } From 0d65bce3ae5200f156aa1fcf0a0301974832642b Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Wed, 21 May 2025 00:37:52 +0900 Subject: [PATCH 19/33] refactor: remove unused members from ILuaFileSystem interface --- src/Lua/IO/ILuaFileSystem.cs | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index bea72b88..39f7bd69 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -18,20 +18,10 @@ public interface ILuaIOStream : IDisposable public ValueTask ReadLineAsync(CancellationToken cancellationToken); public ValueTask ReadToEndAsync(CancellationToken cancellationToken); public ValueTask ReadStringAsync(int count, CancellationToken cancellationToken); - public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken); public ValueTask FlushAsync(CancellationToken cancellationToken); public void SetVBuf(string mode, int size); - public long Seek(long offset, SeekOrigin origin); - - public void SetLength(long value); - - public bool CanRead { get; } - public bool CanSeek { get; } - public bool CanWrite { get; } - public long Length { get; } - public long Position { get; set; } } public sealed class FileSystem : ILuaFileSystem @@ -167,19 +157,9 @@ public void SetVBuf(string mode, int size) } public long Seek(long offset, SeekOrigin origin) => innerStream.Seek(offset, origin); - - public void SetLength(long value) => innerStream.SetLength(value); - public bool CanRead => innerStream.CanRead; public bool CanSeek => innerStream.CanSeek; public bool CanWrite => innerStream.CanWrite; - public long Length => innerStream.Length; - - public long Position - { - get => innerStream.Position; - set => innerStream.Position = value; - } public void Dispose() => innerStream.Dispose(); } \ No newline at end of file From f499aef923f67e165eeb0486a09824b17294c1c0 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Wed, 21 May 2025 00:44:48 +0900 Subject: [PATCH 20/33] feat: implement enum LuaFileBufferingMode for SetVBuf method --- src/Lua/IO/ILuaFileSystem.cs | 6 +++--- src/Lua/IO/LuaFileBufferingMode.cs | 19 +++++++++++++++++++ src/Lua/Standard/FileHandle.cs | 9 ++++++++- 3 files changed, 30 insertions(+), 4 deletions(-) create mode 100644 src/Lua/IO/LuaFileBufferingMode.cs diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index 39f7bd69..cc4ac95a 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -20,7 +20,7 @@ public interface ILuaIOStream : IDisposable public ValueTask ReadStringAsync(int count, CancellationToken cancellationToken); public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken); public ValueTask FlushAsync(CancellationToken cancellationToken); - public void SetVBuf(string mode, int size); + public void SetVBuf(LuaFileBufferingMode mode, int size); public long Seek(long offset, SeekOrigin origin); } @@ -149,11 +149,11 @@ public ValueTask FlushAsync(CancellationToken cancellationToken) return new(); } - public void SetVBuf(string mode, int size) + public void SetVBuf(LuaFileBufferingMode mode, int size) { writer ??= new(innerStream); // Ignore size parameter - writer.AutoFlush = mode is "no" or "line"; + writer.AutoFlush = mode is LuaFileBufferingMode.NoBuffering or LuaFileBufferingMode.LineBuffering; } public long Seek(long offset, SeekOrigin origin) => innerStream.Seek(offset, origin); diff --git a/src/Lua/IO/LuaFileBufferingMode.cs b/src/Lua/IO/LuaFileBufferingMode.cs new file mode 100644 index 00000000..60e7a30f --- /dev/null +++ b/src/Lua/IO/LuaFileBufferingMode.cs @@ -0,0 +1,19 @@ +namespace Lua.IO; + +public enum LuaFileBufferingMode +{ + /// + /// No buffering. `no` in Lua + /// + NoBuffering, + + /// + /// Full buffering `full` in Lua + /// + FullBuffering, + + /// + /// Line buffering `line` in Lua + /// + LineBuffering, +} \ No newline at end of file diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index 93f93a14..290e9a0b 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -91,7 +91,14 @@ public ValueTask FlushAsync(CancellationToken cancellationToken) public void SetVBuf(string mode, int size) { - stream.SetVBuf(mode, 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 void Close() From e9c4b276e53429f61e21cc6cfb4af3241980b5d5 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Wed, 21 May 2025 01:21:47 +0900 Subject: [PATCH 21/33] test: add abstract file read tests --- tests/Lua.Tests/AbstractFileTests.cs | 75 ++++++++ tests/Lua.Tests/Helpers/CharMemoryStream.cs | 102 ++++++++++ tests/Lua.Tests/Helpers/IOThrowHelpers.cs | 181 ++++++++++++++++++ .../NotImplementedExceptionFileSystemBase.cs | 39 ++++ .../Helpers/NotSupportedStreamBase.cs | 46 +++++ 5 files changed, 443 insertions(+) create mode 100644 tests/Lua.Tests/AbstractFileTests.cs create mode 100644 tests/Lua.Tests/Helpers/CharMemoryStream.cs create mode 100644 tests/Lua.Tests/Helpers/IOThrowHelpers.cs create mode 100644 tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs create mode 100644 tests/Lua.Tests/Helpers/NotSupportedStreamBase.cs diff --git a/tests/Lua.Tests/AbstractFileTests.cs b/tests/Lua.Tests/AbstractFileTests.cs new file mode 100644 index 00000000..e0af3ee5 --- /dev/null +++ b/tests/Lua.Tests/AbstractFileTests.cs @@ -0,0 +1,75 @@ +using Lua.IO; +using Lua.Standard; +using Lua.Tests.Helpers; + +namespace Lua.Tests; + +public class AbstractFileTests +{ + class ReadOnlyFileSystem(Dictionary dictionary) : NotImplementedExceptionFileSystemBase + { + public override ILuaIOStream? Open(string path, LuaFileOpenMode mode, bool throwError) + { + if (!dictionary.TryGetValue(path, out var value)) + { + if (!throwError) return null; + throw new FileNotFoundException($"File {path} not found"); + } + + if (mode != LuaFileOpenMode.Read) + throw new NotSupportedException($"File {path} not opened in read mode"); + return new ReadOnlyCharMemoryLuaIOStream(value.AsMemory()); + } + } + + [Test] + public async Task ReadLinesTest() + { + var fileContent = "line1\nline2\r\nline3"; + var fileSystem = new ReadOnlyFileSystem(new() { { "test.txt", fileContent } }); + var state = LuaState.Create(); + state.FileSystem = fileSystem; + state.OpenStandardLibraries(); + try + { + await state.DoStringAsync( + """ + local lines = {} + for line in io.lines("test1.txt") do + table.insert(lines, line) + print(line) + end + assert(#lines == 3, "Expected 3 lines") + assert(lines[1] == "line1", "Expected line1") + assert(lines[2] == "line2", "Expected line2") + assert(lines[3] == "line3", "Expected line3") + """); + } + catch (Exception e) + { + Console.WriteLine(e); + throw; + } + } + + [Test] + public async Task ReadFileTest() + { + var fileContent = "Hello, World!"; + var fileSystem = new ReadOnlyFileSystem(new() { { "test.txt", fileContent } }); + var state = LuaState.Create(); + state.FileSystem = fileSystem; + state.OpenStandardLibraries(); + + await state.DoStringAsync( + """ + local file = io.open("test.txt", "r") + assert(file, "Failed to open file") + local content = file:read("*a") + assert(content == "Hello, World!", "Expected 'Hello, World!'") + file:close() + file = io.open("test2.txt", "r") + assert(file == nil, "Expected file to be nil") + """); + } +} \ No newline at end of file diff --git a/tests/Lua.Tests/Helpers/CharMemoryStream.cs b/tests/Lua.Tests/Helpers/CharMemoryStream.cs new file mode 100644 index 00000000..b6c5f9ba --- /dev/null +++ b/tests/Lua.Tests/Helpers/CharMemoryStream.cs @@ -0,0 +1,102 @@ +using Lua.IO; +namespace Lua.Tests.Helpers; + +internal sealed class ReadOnlyCharMemoryLuaIOStream(ReadOnlyMemory buffer, Action? onDispose =null,object? state =null) : NotSupportedStreamBase +{ + public readonly ReadOnlyMemory Buffer = buffer; + int position; + public readonly object? State = state; + Action? onDispose = onDispose; + + public static (string Result, int AdvanceCount) ReadLine(ReadOnlySpan remaining) + { + int advanceCount; + var line = remaining.IndexOfAny('\n', '\r'); + if (line == -1) + { + line = remaining.Length; + advanceCount = line; + } + else + { + if (remaining[line] == '\r' && line + 1 < remaining.Length && remaining[line + 1] == '\n') + { + advanceCount = line + 2; + } + else + { + advanceCount = line + 1; + } + } + + + return new(remaining[..line].ToString(), advanceCount); + } + public override ValueTask ReadLineAsync(CancellationToken cancellationToken) + { + if (position >= Buffer.Length) + { + return new(default(string)); + } + + var remaining = Buffer[position..]; + var (line, advanceCount) = ReadLine(remaining.Span); + position += advanceCount; + return new(line); + } + + public override ValueTask ReadToEndAsync(CancellationToken cancellationToken) + { + if (position >= Buffer.Length) + { + return new(string.Empty); + } + + var remaining = Buffer[position..]; + position = Buffer.Length; + return new(remaining.ToString()); + } + + public override ValueTask ReadStringAsync(int count, CancellationToken cancellationToken) + { + cancellationToken .ThrowIfCancellationRequested(); + if (position >= Buffer.Length) + { + return new(""); + } + + var remaining = Buffer[position..]; + if (count > remaining.Length) + { + count = remaining.Length; + } + + var result = remaining.Slice(0, count).ToString(); + position += count; + return new(result); + } + + public override void Dispose() + { + onDispose?.Invoke(this); + onDispose = null; + } + + public override long Seek(long offset, SeekOrigin origin) + { + unchecked + { + position = origin switch + { + SeekOrigin.Begin => (int)offset, + SeekOrigin.Current => position + (int)offset, + SeekOrigin.End => (int)(Buffer.Length + offset), + _ => (int)IOThrowHelpers.ThrowArgumentExceptionForSeekOrigin() + }; + } + + IOThrowHelpers.ValidatePosition(position, Buffer.Length); + + return position; + } +} \ No newline at end of file diff --git a/tests/Lua.Tests/Helpers/IOThrowHelpers.cs b/tests/Lua.Tests/Helpers/IOThrowHelpers.cs new file mode 100644 index 00000000..513bbdd1 --- /dev/null +++ b/tests/Lua.Tests/Helpers/IOThrowHelpers.cs @@ -0,0 +1,181 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +// CommunityToolkit.HighPerformance.Streams + +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +namespace Lua.Tests.Helpers; + +internal static class IOThrowHelpers +{ + /// + /// Validates the argument (it needs to be in the [0, length]) range. + /// + /// The new value being set. + /// The maximum length of the target . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidatePosition(long position, int length) + { + if ((ulong)position > (ulong)length) + { + ThrowArgumentOutOfRangeExceptionForPosition(); + } + } + + /// + /// Validates the argument (it needs to be in the [0, length]) range. + /// + /// The new value being set. + /// The maximum length of the target . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidatePosition(long position, long length) + { + if ((ulong)position > (ulong)length) + { + ThrowArgumentOutOfRangeExceptionForPosition(); + } + } + + /// + /// Validates the or arguments. + /// + /// The target array. + /// The offset within . + /// The number of elements to process within . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateBuffer([NotNull] byte[]? buffer, int offset, int count) + { + if (buffer is null) + { + ThrowArgumentNullExceptionForBuffer(); + } + + if (offset < 0) + { + ThrowArgumentOutOfRangeExceptionForOffset(); + } + + if (count < 0) + { + ThrowArgumentOutOfRangeExceptionForCount(); + } + + if (offset + count > buffer!.Length) + { + ThrowArgumentExceptionForLength(); + } + } + + /// + /// Validates the CanWrite property. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateCanWrite(bool canWrite) + { + if (!canWrite) + { + ThrowNotSupportedException(); + } + } + + /// + /// Validates that a given instance hasn't been disposed. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static void ValidateDisposed(bool disposed) + { + if (disposed) + { + ThrowObjectDisposedException(); + } + } + /// + /// Gets a standard instance for a stream. + /// + /// A with the standard text. + public static Exception GetNotSupportedException() + { + return new NotSupportedException("The requested operation is not supported for this stream."); + } + + /// + /// Throws a when trying to perform a not supported operation. + /// + [DoesNotReturn] + public static void ThrowNotSupportedException() + { + throw GetNotSupportedException(); + } + + /// + /// Throws an when trying to write too many bytes to the target stream. + /// + [DoesNotReturn] + public static void ThrowArgumentExceptionForEndOfStreamOnWrite() + { + throw new ArgumentException("The current stream can't contain the requested input data."); + } + + /// + /// Throws an when using an invalid seek mode. + /// + /// Nothing, as this method throws unconditionally. + public static long ThrowArgumentExceptionForSeekOrigin() + { + throw new ArgumentException("The input seek mode is not valid.", "origin"); + } + + /// + /// Throws an when setting the property. + /// + private static void ThrowArgumentOutOfRangeExceptionForPosition() + { + throw new ArgumentOutOfRangeException(nameof(Stream.Position), "The value for the property was not in the valid range."); + } + + /// + /// Throws an when an input buffer is . + /// + [DoesNotReturn] + private static void ThrowArgumentNullExceptionForBuffer() + { + throw new ArgumentNullException("buffer", "The buffer is null."); + } + + /// + /// Throws an when the input count is negative. + /// + [DoesNotReturn] + private static void ThrowArgumentOutOfRangeExceptionForOffset() + { + throw new ArgumentOutOfRangeException("offset", "Offset can't be negative."); + } + + /// + /// Throws an when the input count is negative. + /// + [DoesNotReturn] + private static void ThrowArgumentOutOfRangeExceptionForCount() + { + throw new ArgumentOutOfRangeException("count", "Count can't be negative."); + } + + /// + /// Throws an when the sum of offset and count exceeds the length of the target buffer. + /// + [DoesNotReturn] + private static void ThrowArgumentExceptionForLength() + { + throw new ArgumentException("The sum of offset and count can't be larger than the buffer length.", "buffer"); + } + + /// + /// Throws an when using a disposed instance. + /// + [DoesNotReturn] + private static void ThrowObjectDisposedException() + { + throw new ObjectDisposedException("source", "The current stream has already been disposed"); + } +} \ No newline at end of file diff --git a/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs b/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs new file mode 100644 index 00000000..67a2c7d0 --- /dev/null +++ b/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs @@ -0,0 +1,39 @@ +using Lua.IO; + +namespace Lua.Tests.Helpers +{ + abstract class NotImplementedExceptionFileSystemBase : ILuaFileSystem + { + public virtual bool IsReadable(string path) + { + throw new NotImplementedException(); + } + + public virtual ValueTask ReadFileContentAsync(string fileName, CancellationToken cancellationToken) + { + throw new NotImplementedException(); + } + + public virtual ILuaIOStream? Open(string path, LuaFileOpenMode mode, bool throwError) + { + throw new NotImplementedException(); + } + + public virtual void Rename(string oldName, string newName) + { + throw new NotImplementedException(); + } + + public virtual void Remove(string path) + { + throw new NotImplementedException(); + } + + public virtual string DirectorySeparator => Path.DirectorySeparatorChar.ToString(); + + public virtual string GetTempFileName() + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/tests/Lua.Tests/Helpers/NotSupportedStreamBase.cs b/tests/Lua.Tests/Helpers/NotSupportedStreamBase.cs new file mode 100644 index 00000000..430a8082 --- /dev/null +++ b/tests/Lua.Tests/Helpers/NotSupportedStreamBase.cs @@ -0,0 +1,46 @@ +using Lua.IO; + +namespace Lua.Tests.Helpers +{ + public class NotSupportedStreamBase : ILuaIOStream + { + public virtual void Dispose() + { + } + + public virtual ValueTask ReadLineAsync(CancellationToken cancellationToken) + { + throw IOThrowHelpers.GetNotSupportedException(); + } + + public virtual ValueTask ReadToEndAsync(CancellationToken cancellationToken) + { + throw IOThrowHelpers.GetNotSupportedException(); + } + + public virtual ValueTask ReadStringAsync(int count, CancellationToken cancellationToken) + { + throw IOThrowHelpers.GetNotSupportedException(); + } + + public virtual ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) + { + throw IOThrowHelpers.GetNotSupportedException(); + } + + public virtual ValueTask FlushAsync(CancellationToken cancellationToken) + { + throw IOThrowHelpers.GetNotSupportedException(); + } + + public virtual void SetVBuf(LuaFileBufferingMode mode, int size) + { + throw IOThrowHelpers.GetNotSupportedException(); + } + + public virtual long Seek(long offset, SeekOrigin origin) + { + throw IOThrowHelpers.GetNotSupportedException(); + } + } +} \ No newline at end of file From 2ef80815889f7e8f7cec9db5219f0fabd6c82b77 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Wed, 21 May 2025 10:50:40 +0900 Subject: [PATCH 22/33] fix: io exception was not thrown well --- src/Lua/IO/ILuaFileSystem.cs | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index cc4ac95a..e261a5d3 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -64,6 +64,11 @@ public ValueTask ReadFileContentAsync(string path, CancellationT public ILuaIOStream? Open(string path, LuaFileOpenMode luaMode, bool throwError) { + if (luaMode == LuaFileOpenMode.ReadAppend) + { + throw new NotSupportedException("a+ mode is not supported."); + } + var (mode, access) = GetFileMode(luaMode); try { @@ -108,6 +113,7 @@ public sealed class LuaIOStreamWrapper(Stream innerStream) : ILuaIOStream public ValueTask ReadLineAsync(CancellationToken cancellationToken) { + ThrowIfNotReadable(); reader ??= new(innerStream); return new(reader.ReadLine()); @@ -115,6 +121,7 @@ public sealed class LuaIOStreamWrapper(Stream innerStream) : ILuaIOStream public ValueTask ReadToEndAsync(CancellationToken cancellationToken) { + ThrowIfNotReadable(); reader ??= new(innerStream); return new(reader.ReadToEnd()); @@ -122,6 +129,7 @@ public ValueTask ReadToEndAsync(CancellationToken cancellationToken) public ValueTask ReadStringAsync(int count, CancellationToken cancellationToken) { + ThrowIfNotReadable(); reader ??= new(innerStream); using var byteBuffer = new PooledArray(count); @@ -137,15 +145,16 @@ public ValueTask ReadToEndAsync(CancellationToken cancellationToken) public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) { + ThrowIfNotWritable(); writer ??= new(innerStream); - writer.Write(buffer.Span); return new(); } public ValueTask FlushAsync(CancellationToken cancellationToken) { - innerStream.Flush(); + ThrowIfNotWritable(); + writer?.Flush(); return new(); } @@ -161,5 +170,21 @@ public void SetVBuf(LuaFileBufferingMode mode, int size) public bool CanSeek => innerStream.CanSeek; public bool CanWrite => innerStream.CanWrite; + void ThrowIfNotReadable() + { + if (!innerStream.CanRead) + { + throw new IOException("Stream is not readable."); + } + } + + void ThrowIfNotWritable() + { + if (!innerStream.CanWrite) + { + throw new IOException("Stream is not writable."); + } + } + public void Dispose() => innerStream.Dispose(); } \ No newline at end of file From ca04ade8bad55905561e6b2f1d6a51f9a57ccd2e Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Wed, 21 May 2025 10:55:02 +0900 Subject: [PATCH 23/33] add: io.tmpfile --- src/Lua/IO/ILuaFileSystem.cs | 6 ++++++ src/Lua/Standard/IOLibrary.cs | 9 +++++++++ .../Helpers/NotImplementedExceptionFileSystemBase.cs | 7 +++++++ 3 files changed, 22 insertions(+) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index e261a5d3..67860b2c 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -11,6 +11,7 @@ public interface ILuaFileSystem public void Remove(string path); public string DirectorySeparator { get; } public string GetTempFileName(); + public ILuaIOStream OpenTempFileStream(); } public interface ILuaIOStream : IDisposable @@ -104,6 +105,11 @@ public string GetTempFileName() { return Path.GetTempFileName(); } + + public ILuaIOStream OpenTempFileStream() + { + return new LuaIOStreamWrapper(File.Open(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite)); + } } public sealed class LuaIOStreamWrapper(Stream innerStream) : ILuaIOStream diff --git a/src/Lua/Standard/IOLibrary.cs b/src/Lua/Standard/IOLibrary.cs index 1e8e695e..4fa89a30 100644 --- a/src/Lua/Standard/IOLibrary.cs +++ b/src/Lua/Standard/IOLibrary.cs @@ -21,6 +21,7 @@ public IOLibrary() new("read", Read), new("type", Type), new("write", Write), + new("tmpfile", TmpFile), ]; } @@ -195,4 +196,12 @@ public async ValueTask Write(LuaFunctionExecutionContext context, Cancellat var resultCount = await IOHelper.WriteAsync(file, "write", context, cancellationToken); return resultCount; } + + public async ValueTask TmpFile(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + { + var file = context.State.Registry["stdout"].Read(); + var resultCount = await IOHelper.WriteAsync(file, "write", context, cancellationToken); + return resultCount; + } + } \ No newline at end of file diff --git a/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs b/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs index 67a2c7d0..1ede781d 100644 --- a/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs +++ b/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs @@ -35,5 +35,12 @@ public virtual string GetTempFileName() { throw new NotImplementedException(); } + + public virtual ILuaIOStream OpenTempStream () + { + throw new NotImplementedException(); + } + + } } \ No newline at end of file From 3b655f32c9cd585d5f815c2eb3f0883a9c3a7aa9 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Wed, 21 May 2025 10:56:02 +0900 Subject: [PATCH 24/33] fix: compile error --- .../Helpers/NotImplementedExceptionFileSystemBase.cs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs b/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs index 1ede781d..60ebb7a9 100644 --- a/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs +++ b/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs @@ -35,12 +35,10 @@ public virtual string GetTempFileName() { throw new NotImplementedException(); } - - public virtual ILuaIOStream OpenTempStream () + + public ILuaIOStream OpenTempFileStream() { throw new NotImplementedException(); } - - } } \ No newline at end of file From fd686dafaf625d53e93d3526df7689de9511bc52 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Thu, 22 May 2025 01:15:03 +0900 Subject: [PATCH 25/33] fix : ILuaIOStream open mode behavior --- src/Lua/IO/ILuaFileSystem.cs | 110 ++++++---- src/Lua/Internal/Utf8Reader.cs | 191 ++++++++++++++++++ src/Lua/Standard/FileHandle.cs | 2 +- src/Lua/Standard/OpenLibsExtensions.cs | 7 +- .../Helpers/NotSupportedStreamBase.cs | 4 +- 5 files changed, 273 insertions(+), 41 deletions(-) create mode 100644 src/Lua/Internal/Utf8Reader.cs diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index 67860b2c..e9dc13e1 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -1,4 +1,5 @@ using Lua.Internal; +using System.Text; namespace Lua.IO; @@ -16,6 +17,7 @@ public interface ILuaFileSystem public interface ILuaIOStream : IDisposable { + public LuaFileOpenMode Mode { get; } public ValueTask ReadLineAsync(CancellationToken cancellationToken); public ValueTask ReadToEndAsync(CancellationToken cancellationToken); public ValueTask ReadStringAsync(int count, CancellationToken cancellationToken); @@ -23,6 +25,11 @@ public interface ILuaIOStream : IDisposable public ValueTask FlushAsync(CancellationToken cancellationToken); public void SetVBuf(LuaFileBufferingMode mode, int size); public long Seek(long offset, SeekOrigin origin); + + public static ILuaIOStream CreateStreamWrapper(LuaFileOpenMode mode, Stream stream) + { + return new LuaIOStreamWrapper(mode, stream); + } } public sealed class FileSystem : ILuaFileSystem @@ -37,7 +44,7 @@ public static (FileMode, FileAccess access) GetFileMode(LuaFileOpenMode luaFileO LuaFileOpenMode.Write => (FileMode.Create, FileAccess.Write), LuaFileOpenMode.Append => (FileMode.Append, FileAccess.Write), LuaFileOpenMode.ReadWriteOpen => (FileMode.Open, FileAccess.ReadWrite), - LuaFileOpenMode.ReadWriteCreate => (FileMode.Create, FileAccess.ReadWrite), + LuaFileOpenMode.ReadWriteCreate => (FileMode.Truncate, FileAccess.ReadWrite), LuaFileOpenMode.ReadAppend => (FileMode.Append, FileAccess.ReadWrite), _ => throw new ArgumentOutOfRangeException(nameof(luaFileOpenMode), luaFileOpenMode, null) }; @@ -65,15 +72,17 @@ public ValueTask ReadFileContentAsync(string path, CancellationT public ILuaIOStream? Open(string path, LuaFileOpenMode luaMode, bool throwError) { - if (luaMode == LuaFileOpenMode.ReadAppend) - { - throw new NotSupportedException("a+ mode is not supported."); - } - var (mode, access) = GetFileMode(luaMode); try { - return new LuaIOStreamWrapper(File.Open(path, mode, access)); + if (luaMode == LuaFileOpenMode.ReadAppend) + { + var s = new LuaIOStreamWrapper(luaMode, File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete)); + s.Seek(0, SeekOrigin.End); + return s; + } + + return new LuaIOStreamWrapper(luaMode, File.Open(path, mode, access, FileShare.ReadWrite | FileShare.Delete)); } catch (Exception) { @@ -108,70 +117,95 @@ public string GetTempFileName() public ILuaIOStream OpenTempFileStream() { - return new LuaIOStreamWrapper(File.Open(Path.GetTempFileName(), FileMode.OpenOrCreate, FileAccess.ReadWrite)); + return new LuaIOStreamWrapper(LuaFileOpenMode.ReadAppend, File.Open(Path.GetTempFileName(), FileMode.Open, FileAccess.ReadWrite)); } } -public sealed class LuaIOStreamWrapper(Stream innerStream) : ILuaIOStream +internal sealed class LuaIOStreamWrapper(LuaFileOpenMode mode, Stream innerStream) : ILuaIOStream { - StreamReader? reader; - StreamWriter? writer; + public LuaFileOpenMode Mode => mode; + Utf8Reader? reader; + ulong flushSize = ulong.MaxValue; + ulong nextFlushSize = ulong.MaxValue; public ValueTask ReadLineAsync(CancellationToken cancellationToken) { ThrowIfNotReadable(); - reader ??= new(innerStream); - - return new(reader.ReadLine()); + reader ??= new(); + return new(reader.ReadLine(innerStream)); } public ValueTask ReadToEndAsync(CancellationToken cancellationToken) { ThrowIfNotReadable(); - reader ??= new(innerStream); - - return new(reader.ReadToEnd()); + reader ??= new(); + return new(reader.ReadToEnd(innerStream)); } public ValueTask ReadStringAsync(int count, CancellationToken cancellationToken) { ThrowIfNotReadable(); - reader ??= new(innerStream); - - using var byteBuffer = new PooledArray(count); - var span = byteBuffer.AsSpan(); - var ret = reader.Read(span); - if (ret != span.Length) - { - return new(default(string)); - } - - return new(span.ToString()); + reader ??= new(); + return new(reader.Read(innerStream, count)); } public ValueTask WriteAsync(ReadOnlyMemory buffer, CancellationToken cancellationToken) { ThrowIfNotWritable(); - writer ??= new(innerStream); - writer.Write(buffer.Span); + if (mode is LuaFileOpenMode.Append or LuaFileOpenMode.ReadAppend) + { + innerStream.Seek(0, SeekOrigin.End); + } + + using var byteBuffer = new PooledArray(4096); + var encoder = Encoding.UTF8.GetEncoder(); + var totalBytes = encoder.GetByteCount(buffer.Span, true); + var remainingBytes = totalBytes; + while (0 < remainingBytes) + { + var byteCount = encoder.GetBytes(buffer.Span, byteBuffer.AsSpan(), false); + innerStream.Write(byteBuffer.AsSpan()[..byteCount]); + remainingBytes -= byteCount; + } + + if (nextFlushSize < (ulong)totalBytes) + { + innerStream.Flush(); + nextFlushSize = flushSize; + } + + reader?.Clear(); return new(); } public ValueTask FlushAsync(CancellationToken cancellationToken) { - ThrowIfNotWritable(); - writer?.Flush(); + innerStream.Flush(); + nextFlushSize = flushSize; return new(); } public void SetVBuf(LuaFileBufferingMode mode, int size) { - writer ??= new(innerStream); // Ignore size parameter - writer.AutoFlush = mode is LuaFileBufferingMode.NoBuffering or LuaFileBufferingMode.LineBuffering; + if (mode is LuaFileBufferingMode.NoBuffering or LuaFileBufferingMode.LineBuffering) + { + nextFlushSize = 0; + flushSize = 0; + } + else + { + nextFlushSize = (ulong)size; + flushSize = (ulong)size; + } + } + + public long Seek(long offset, SeekOrigin origin) + { + reader?.Clear(); + return innerStream.Seek(offset, origin); } - public long Seek(long offset, SeekOrigin origin) => innerStream.Seek(offset, origin); public bool CanRead => innerStream.CanRead; public bool CanSeek => innerStream.CanSeek; public bool CanWrite => innerStream.CanWrite; @@ -192,5 +226,9 @@ void ThrowIfNotWritable() } } - public void Dispose() => innerStream.Dispose(); + public void Dispose() + { + innerStream.Dispose(); + reader?.Dispose(); + } } \ No newline at end of file diff --git a/src/Lua/Internal/Utf8Reader.cs b/src/Lua/Internal/Utf8Reader.cs new file mode 100644 index 00000000..2c4af82c --- /dev/null +++ b/src/Lua/Internal/Utf8Reader.cs @@ -0,0 +1,191 @@ +using System.Buffers; +using System.Text; + +namespace Lua.Internal; + +internal sealed class Utf8Reader +{ + [ThreadStatic] + static byte[]? scratchBuffer; + + [ThreadStatic] + internal static bool scratchBufferUsed; + + private readonly byte[] buffer; + private int bufPos, bufLen; + private Decoder? decoder; + + const int ThreadStaticBufferSize = 1024; + + public Utf8Reader() + { + if (scratchBufferUsed) + { + buffer = new byte[ThreadStaticBufferSize]; + return; + } + + scratchBuffer ??= new byte[ThreadStaticBufferSize]; + + buffer = scratchBuffer; + scratchBufferUsed = true; + } + + public string? ReadLine(Stream stream) + { + var resultBuffer = ArrayPool.Shared.Rent(1024); + var lineLen = 0; + try + { + while (true) + { + if (bufPos >= bufLen) + { + bufLen = stream.Read(buffer, 0, buffer.Length); + bufPos = 0; + if (bufLen == 0) + break; // EOF + } + + var span = new Span(buffer, bufPos, bufLen - bufPos); + int idx = span.IndexOfAny((byte)'\r', (byte)'\n'); + + if (idx >= 0) + { + AppendToBuffer(ref resultBuffer, span[..idx], ref lineLen); + + byte nl = span[idx]; + bufPos += idx + 1; + + // CRLF + if (nl == (byte)'\r' && bufPos < bufLen && buffer[bufPos] == (byte)'\n') + bufPos++; + + // 行を返す + return Encoding.UTF8.GetString(resultBuffer, 0, lineLen); + } + else + { + // 改行なし → 全部行バッファへ + AppendToBuffer(ref resultBuffer, span, ref lineLen); + bufPos = bufLen; + } + } + + if (lineLen == 0) + return null; + return Encoding.UTF8.GetString(resultBuffer, 0, lineLen); + } + finally + { + ArrayPool.Shared.Return(resultBuffer); + } + } + + public string ReadToEnd(Stream stream) + { + var resultBuffer = ArrayPool.Shared.Rent(1024); + var len = 0; + try + { + while (true) + { + if (bufPos >= bufLen) + { + bufLen = stream.Read(buffer, 0, buffer.Length); + bufPos = 0; + if (bufLen == 0) + break; // EOF + } + + var span = new Span(buffer, bufPos, bufLen - bufPos); + AppendToBuffer(ref resultBuffer, span, ref len); + bufPos = bufLen; + } + + if (len == 0) + return ""; + return Encoding.UTF8.GetString(resultBuffer, 0, len); + } + finally + { + ArrayPool.Shared.Return(resultBuffer); + } + } + + public string? Read(Stream stream, int charCount) + { + if (charCount < 0) throw new ArgumentOutOfRangeException(nameof(charCount)); + if (charCount == 0) return string.Empty; + + var len = 0; + bool dataRead = false; + var resultBuffer = ArrayPool.Shared.Rent(charCount); + + try + { + while (len < charCount) + { + if (bufPos >= bufLen) + { + bufLen = stream.Read(buffer, 0, buffer.Length); + bufPos = 0; + if (bufLen == 0) break; // EOF + } + + var byteSpan = new ReadOnlySpan(buffer, bufPos, bufLen - bufPos); + var charSpan = new Span(resultBuffer, len, charCount - len); + decoder ??= Encoding.UTF8.GetDecoder(); + decoder.Convert( + byteSpan, + charSpan, + flush: false, + out int bytesUsed, + out int charsUsed, + out _); + + if (charsUsed > 0) + { + len += charsUsed; + dataRead = true; + } + + bufPos += bytesUsed; + if (bytesUsed == 0) break; + } + + if (!dataRead || len != charCount) return null; + return resultBuffer.AsSpan(0, len).ToString(); + } + finally + { + ArrayPool.Shared.Return(resultBuffer); + } + } + + + private static void AppendToBuffer(ref byte[] buffer, ReadOnlySpan segment, ref int length) + { + if (length + segment.Length > buffer.Length) + { + int newSize = Math.Max(buffer.Length * 2, length + segment.Length); + var newBuffer = ArrayPool.Shared.Rent(newSize); + Array.Copy(buffer, newBuffer, length); + ArrayPool.Shared.Return(buffer); + } + + segment.CopyTo(buffer.AsSpan(length)); + length += segment.Length; + } + + public void Clear() + { + bufPos = 0; + bufLen = 0; + } + + public void Dispose() + { + scratchBufferUsed = false; + } +} \ No newline at end of file diff --git a/src/Lua/Standard/FileHandle.cs b/src/Lua/Standard/FileHandle.cs index 290e9a0b..1698b200 100644 --- a/src/Lua/Standard/FileHandle.cs +++ b/src/Lua/Standard/FileHandle.cs @@ -48,7 +48,7 @@ static FileHandle() fileHandleMetatable[Metamethods.Index] = IndexMetamethod; } - public FileHandle(Stream stream) : this(new LuaIOStreamWrapper(stream)) { } + public FileHandle(LuaFileOpenMode mode, Stream stream) : this(new LuaIOStreamWrapper(mode,stream)) { } public FileHandle(ILuaIOStream stream) { diff --git a/src/Lua/Standard/OpenLibsExtensions.cs b/src/Lua/Standard/OpenLibsExtensions.cs index 09b10422..1831f584 100644 --- a/src/Lua/Standard/OpenLibsExtensions.cs +++ b/src/Lua/Standard/OpenLibsExtensions.cs @@ -1,3 +1,4 @@ +using Lua.IO; using Lua.Runtime; using Lua.Standard.Internal; @@ -47,9 +48,9 @@ public static void OpenIOLibrary(this LuaState state) } var registry = state.Registry; - registry["stdin"] = new(new FileHandle(ConsoleHelper.OpenStandardInput())); - registry["stdout"] = new(new FileHandle(ConsoleHelper.OpenStandardOutput())); - registry["stderr"] = new(new FileHandle(ConsoleHelper.OpenStandardError())); + registry["stdin"] = new(new FileHandle(LuaFileOpenMode.Read, ConsoleHelper.OpenStandardInput())); + registry["stdout"] = new(new FileHandle(LuaFileOpenMode.Write, ConsoleHelper.OpenStandardOutput())); + registry["stderr"] = new(new FileHandle(LuaFileOpenMode.Write, ConsoleHelper.OpenStandardError())); state.Environment["io"] = io; state.LoadedModules["io"] = io; diff --git a/tests/Lua.Tests/Helpers/NotSupportedStreamBase.cs b/tests/Lua.Tests/Helpers/NotSupportedStreamBase.cs index 430a8082..5c9ffc93 100644 --- a/tests/Lua.Tests/Helpers/NotSupportedStreamBase.cs +++ b/tests/Lua.Tests/Helpers/NotSupportedStreamBase.cs @@ -8,6 +8,8 @@ public virtual void Dispose() { } + public virtual LuaFileOpenMode Mode => throw IOThrowHelpers.GetNotSupportedException(); + public virtual ValueTask ReadLineAsync(CancellationToken cancellationToken) { throw IOThrowHelpers.GetNotSupportedException(); @@ -38,7 +40,7 @@ public virtual void SetVBuf(LuaFileBufferingMode mode, int size) throw IOThrowHelpers.GetNotSupportedException(); } - public virtual long Seek(long offset, SeekOrigin origin) + public virtual long Seek(long offset, SeekOrigin origin) { throw IOThrowHelpers.GetNotSupportedException(); } From c72b8b55ab57610a9d5a64f23fb3fd793ed49129 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Thu, 22 May 2025 01:17:36 +0900 Subject: [PATCH 26/33] fix: FileSystem.OpenTempFileStream is not used --- src/Lua/Standard/IOLibrary.cs | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/src/Lua/Standard/IOLibrary.cs b/src/Lua/Standard/IOLibrary.cs index 4fa89a30..a63052a3 100644 --- a/src/Lua/Standard/IOLibrary.cs +++ b/src/Lua/Standard/IOLibrary.cs @@ -76,7 +76,7 @@ public ValueTask Input(LuaFunctionExecutionContext context, CancellationTok } else { - var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen,true)!; + var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen, true)!; var handle = new FileHandle(stream); registry["stdin"] = new(handle); return new(context.Return(new LuaValue(handle))); @@ -160,7 +160,7 @@ public ValueTask Output(LuaFunctionExecutionContext context, CancellationTo } else { - var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen,true)!; + var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen, true)!; var handle = new FileHandle(stream); io["stdout"] = new(handle); return new(context.Return(new LuaValue(handle))); @@ -170,9 +170,10 @@ public ValueTask Output(LuaFunctionExecutionContext context, CancellationTo public async ValueTask Read(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { var file = context.State.Registry["stdin"].Read(); + var args = context.Arguments.ToArray(); context.Return(); - var resultCount = await IOHelper.ReadAsync(context.Thread, file, "read", 0, context.Arguments.ToArray(), false, cancellationToken); + var resultCount = await IOHelper.ReadAsync(context.Thread, file, "read", 0, args, false, cancellationToken); return resultCount; } @@ -196,12 +197,9 @@ public async ValueTask Write(LuaFunctionExecutionContext context, Cancellat var resultCount = await IOHelper.WriteAsync(file, "write", context, cancellationToken); return resultCount; } - - public async ValueTask TmpFile(LuaFunctionExecutionContext context, CancellationToken cancellationToken) + + public ValueTask TmpFile(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { - var file = context.State.Registry["stdout"].Read(); - var resultCount = await IOHelper.WriteAsync(file, "write", context, cancellationToken); - return resultCount; + return new(context.Return(LuaValue.FromUserData(new FileHandle(context.State.FileSystem.OpenTempFileStream())))); } - } \ No newline at end of file From ecdf1a8b05eb9d8d611ee2085b385cebabe99276 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Thu, 22 May 2025 01:30:16 +0900 Subject: [PATCH 27/33] fix: update io module to include stdin, stdout, and stderr --- src/Lua/Standard/OpenLibsExtensions.cs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/Lua/Standard/OpenLibsExtensions.cs b/src/Lua/Standard/OpenLibsExtensions.cs index 1831f584..bbc00eb3 100644 --- a/src/Lua/Standard/OpenLibsExtensions.cs +++ b/src/Lua/Standard/OpenLibsExtensions.cs @@ -48,9 +48,15 @@ public static void OpenIOLibrary(this LuaState state) } var registry = state.Registry; - registry["stdin"] = new(new FileHandle(LuaFileOpenMode.Read, ConsoleHelper.OpenStandardInput())); - registry["stdout"] = new(new FileHandle(LuaFileOpenMode.Write, ConsoleHelper.OpenStandardOutput())); - registry["stderr"] = new(new FileHandle(LuaFileOpenMode.Write, ConsoleHelper.OpenStandardError())); + var stdin = new LuaValue(new FileHandle(LuaFileOpenMode.Read, ConsoleHelper.OpenStandardInput())); + var stdout = new LuaValue(new FileHandle(LuaFileOpenMode.Write, ConsoleHelper.OpenStandardOutput())); + var stderr = new LuaValue(new FileHandle(LuaFileOpenMode.Write, ConsoleHelper.OpenStandardError())); + registry["stdin"] = stdin; + registry["stdout"] = stdout; + registry["stderr"] = stderr; + io["stdin"] = stdin; + io["stdout"] = stdout; + io["stderr"] = stderr; state.Environment["io"] = io; state.LoadedModules["io"] = io; From 678a2fe0405dbb8b6a838c1b16588c5266f14a75 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Thu, 22 May 2025 01:44:37 +0900 Subject: [PATCH 28/33] change to throw open error always --- src/Lua/IO/ILuaFileSystem.cs | 28 ++++++------------- src/Lua/Standard/IOLibrary.cs | 19 +++++++++---- src/Lua/Standard/Internal/IOHelper.cs | 6 +--- .../NotImplementedExceptionFileSystemBase.cs | 2 +- 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index e9dc13e1..1bf07739 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -7,7 +7,7 @@ public interface ILuaFileSystem { public bool IsReadable(string path); public ValueTask ReadFileContentAsync(string path, CancellationToken cancellationToken); - public ILuaIOStream? Open(string path, LuaFileOpenMode mode, bool throwError); + public ILuaIOStream Open(string path, LuaFileOpenMode mode); public void Rename(string oldName, string newName); public void Remove(string path); public string DirectorySeparator { get; } @@ -70,29 +70,19 @@ public ValueTask ReadFileContentAsync(string path, CancellationT return new(new LuaFileContent(bytes)); } - public ILuaIOStream? Open(string path, LuaFileOpenMode luaMode, bool throwError) + public ILuaIOStream Open(string path, LuaFileOpenMode luaMode) { var (mode, access) = GetFileMode(luaMode); - try + + if (luaMode == LuaFileOpenMode.ReadAppend) { - if (luaMode == LuaFileOpenMode.ReadAppend) - { - var s = new LuaIOStreamWrapper(luaMode, File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete)); - s.Seek(0, SeekOrigin.End); - return s; - } - - return new LuaIOStreamWrapper(luaMode, File.Open(path, mode, access, FileShare.ReadWrite | FileShare.Delete)); + var s = new LuaIOStreamWrapper(luaMode, File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete)); + s.Seek(0, SeekOrigin.End); + return s; } - catch (Exception) - { - if (throwError) - { - throw; - } - return null; - } + return new LuaIOStreamWrapper(luaMode, File.Open(path, mode, access, FileShare.ReadWrite | FileShare.Delete)); + } public void Rename(string oldName, string newName) diff --git a/src/Lua/Standard/IOLibrary.cs b/src/Lua/Standard/IOLibrary.cs index a63052a3..83f8b6e0 100644 --- a/src/Lua/Standard/IOLibrary.cs +++ b/src/Lua/Standard/IOLibrary.cs @@ -76,7 +76,7 @@ public ValueTask Input(LuaFunctionExecutionContext context, CancellationTok } else { - var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen, true)!; + var stream = context.State.FileSystem.Open(arg.ToString(), LuaFileOpenMode.ReadWriteOpen); var handle = new FileHandle(stream); registry["stdin"] = new(handle); return new(context.Return(new LuaValue(handle))); @@ -139,7 +139,7 @@ public ValueTask Open(LuaFunctionExecutionContext context, CancellationToke ? context.GetArgument(1) : "r"; context.Return(); - var resultCount = IOHelper.Open(context.Thread, fileName, mode, false); + var resultCount = IOHelper.Open(context.Thread, fileName, mode, true); return new(resultCount); } @@ -160,10 +160,17 @@ public ValueTask Output(LuaFunctionExecutionContext context, CancellationTo } else { - var stream = context.State.FileSystem.Open(arg.ToString()!, LuaFileOpenMode.ReadWriteOpen, true)!; - var handle = new FileHandle(stream); - io["stdout"] = new(handle); - return new(context.Return(new LuaValue(handle))); + try + { + var stream = context.State.FileSystem.Open(arg.ToString(), LuaFileOpenMode.ReadWriteOpen); + var handle = new FileHandle(stream); + io["stdout"] = new(handle); + return new(context.Return(new LuaValue(handle))); + } + catch (Exception ex) when(ex is IOException or FileNotFoundException) + { + return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult)); + } } } diff --git a/src/Lua/Standard/Internal/IOHelper.cs b/src/Lua/Standard/Internal/IOHelper.cs index 6c89ef78..bb5c1f5f 100644 --- a/src/Lua/Standard/Internal/IOHelper.cs +++ b/src/Lua/Standard/Internal/IOHelper.cs @@ -24,11 +24,7 @@ public static int Open(LuaThread thread, string fileName, string mode, bool thro try { - var stream = thread.State.FileSystem.Open(fileName, fileMode, throwError); - if (stream == null) - { - return 0; - } + var stream = thread.State.FileSystem.Open(fileName, fileMode); thread.Stack.Push(new LuaValue(new FileHandle(stream))); return 1; diff --git a/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs b/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs index 60ebb7a9..73bfadac 100644 --- a/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs +++ b/tests/Lua.Tests/Helpers/NotImplementedExceptionFileSystemBase.cs @@ -14,7 +14,7 @@ public virtual ValueTask ReadFileContentAsync(string fileName, C throw new NotImplementedException(); } - public virtual ILuaIOStream? Open(string path, LuaFileOpenMode mode, bool throwError) + public virtual ILuaIOStream Open(string path, LuaFileOpenMode mode) { throw new NotImplementedException(); } From 705856e547c6befcd80575ff952de278acd37f34 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Thu, 22 May 2025 01:50:31 +0900 Subject: [PATCH 29/33] fix: throw open error --- src/Lua/Standard/IOLibrary.cs | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/Lua/Standard/IOLibrary.cs b/src/Lua/Standard/IOLibrary.cs index 83f8b6e0..db41a3be 100644 --- a/src/Lua/Standard/IOLibrary.cs +++ b/src/Lua/Standard/IOLibrary.cs @@ -139,8 +139,15 @@ public ValueTask Open(LuaFunctionExecutionContext context, CancellationToke ? context.GetArgument(1) : "r"; context.Return(); - var resultCount = IOHelper.Open(context.Thread, fileName, mode, true); - return new(resultCount); + try + { + var resultCount = IOHelper.Open(context.Thread, fileName, mode, true); + return new(resultCount); + } + catch (IOException ex) + { + return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult)); + } } public ValueTask Output(LuaFunctionExecutionContext context, CancellationToken cancellationToken) @@ -160,17 +167,10 @@ public ValueTask Output(LuaFunctionExecutionContext context, CancellationTo } else { - try - { - var stream = context.State.FileSystem.Open(arg.ToString(), LuaFileOpenMode.ReadWriteOpen); - var handle = new FileHandle(stream); - io["stdout"] = new(handle); - return new(context.Return(new LuaValue(handle))); - } - catch (Exception ex) when(ex is IOException or FileNotFoundException) - { - return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult)); - } + var stream = context.State.FileSystem.Open(arg.ToString(), LuaFileOpenMode.ReadWriteOpen); + var handle = new FileHandle(stream); + io["stdout"] = new(handle); + return new(context.Return(new LuaValue(handle))); } } From 8a6a083d57849172d3ae6f09a83052816e37810d Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Thu, 22 May 2025 07:48:43 +0900 Subject: [PATCH 30/33] fix: test compile error --- tests/Lua.Tests/AbstractFileTests.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/Lua.Tests/AbstractFileTests.cs b/tests/Lua.Tests/AbstractFileTests.cs index e0af3ee5..02f16c90 100644 --- a/tests/Lua.Tests/AbstractFileTests.cs +++ b/tests/Lua.Tests/AbstractFileTests.cs @@ -8,16 +8,15 @@ public class AbstractFileTests { class ReadOnlyFileSystem(Dictionary dictionary) : NotImplementedExceptionFileSystemBase { - public override ILuaIOStream? Open(string path, LuaFileOpenMode mode, bool throwError) + public override ILuaIOStream Open(string path, LuaFileOpenMode mode) { if (!dictionary.TryGetValue(path, out var value)) { - if (!throwError) return null; throw new FileNotFoundException($"File {path} not found"); } if (mode != LuaFileOpenMode.Read) - throw new NotSupportedException($"File {path} not opened in read mode"); + throw new IOException($"File {path} not opened in read mode"); return new ReadOnlyCharMemoryLuaIOStream(value.AsMemory()); } } From 5c19191d4931f264269cbe22083309b87651470f Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Thu, 22 May 2025 07:52:39 +0900 Subject: [PATCH 31/33] fix: LuaFileBufferingMode order for c compatibility --- src/Lua/IO/LuaFileBufferingMode.cs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/Lua/IO/LuaFileBufferingMode.cs b/src/Lua/IO/LuaFileBufferingMode.cs index 60e7a30f..b4223c97 100644 --- a/src/Lua/IO/LuaFileBufferingMode.cs +++ b/src/Lua/IO/LuaFileBufferingMode.cs @@ -2,11 +2,6 @@ public enum LuaFileBufferingMode { - /// - /// No buffering. `no` in Lua - /// - NoBuffering, - /// /// Full buffering `full` in Lua /// @@ -16,4 +11,8 @@ public enum LuaFileBufferingMode /// Line buffering `line` in Lua /// LineBuffering, + /// + /// No buffering. `no` in Lua + /// + NoBuffering, } \ No newline at end of file From 772d71ed14cce807132fd2c71a21a413adc87a62 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Thu, 22 May 2025 08:06:16 +0900 Subject: [PATCH 32/33] fix: io registry field naming std -> _IO_ --- src/Lua/Standard/IOLibrary.cs | 22 +++++++++++----------- src/Lua/Standard/OpenLibsExtensions.cs | 5 ++--- 2 files changed, 13 insertions(+), 14 deletions(-) diff --git a/src/Lua/Standard/IOLibrary.cs b/src/Lua/Standard/IOLibrary.cs index db41a3be..f825a8ea 100644 --- a/src/Lua/Standard/IOLibrary.cs +++ b/src/Lua/Standard/IOLibrary.cs @@ -31,7 +31,7 @@ public ValueTask Close(LuaFunctionExecutionContext context, CancellationTok { var file = context.HasArgument(0) ? context.GetArgument(0) - : context.State.Registry["stdout"].Read(); + : context.State.Registry["_IO_output"].Read(); try { @@ -46,7 +46,7 @@ public ValueTask Close(LuaFunctionExecutionContext context, CancellationTok public async ValueTask Flush(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { - var file = context.State.Registry["stdout"].Read(); + var file = context.State.Registry["_IO_output"].Read(); try { @@ -65,20 +65,20 @@ public ValueTask Input(LuaFunctionExecutionContext context, CancellationTok if (context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil) { - return new(context.Return(registry["stdin"])); + return new(context.Return(registry["_IO_input"])); } var arg = context.Arguments[0]; if (arg.TryRead(out var file)) { - registry["stdin"] = new(file); + registry["_IO_input"] = new(file); return new(context.Return(new LuaValue(file))); } else { var stream = context.State.FileSystem.Open(arg.ToString(), LuaFileOpenMode.ReadWriteOpen); var handle = new FileHandle(stream); - registry["stdin"] = new(handle); + registry["_IO_input"] = new(handle); return new(context.Return(new LuaValue(handle))); } } @@ -87,7 +87,7 @@ public ValueTask Lines(LuaFunctionExecutionContext context, CancellationTok { if (context.ArgumentCount == 0) { - var file = context.State.Registry["stdin"].Read(); + var file = context.State.Registry["_IO_input"].Read(); return new(context.Return(new CSharpClosure("iterator", [new(file)], static async (context, cancellationToken) => { var file = context.GetCsClosure()!.UpValues[0].Read(); @@ -156,27 +156,27 @@ public ValueTask Output(LuaFunctionExecutionContext context, CancellationTo if (context.ArgumentCount == 0 || context.Arguments[0].Type is LuaValueType.Nil) { - return new(context.Return(io["stdout"])); + return new(context.Return(io["_IO_output"])); } var arg = context.Arguments[0]; if (arg.TryRead(out var file)) { - io["stdout"] = new(file); + io["_IO_output"] = new(file); return new(context.Return(new LuaValue(file))); } else { var stream = context.State.FileSystem.Open(arg.ToString(), LuaFileOpenMode.ReadWriteOpen); var handle = new FileHandle(stream); - io["stdout"] = new(handle); + io["_IO_output"] = new(handle); return new(context.Return(new LuaValue(handle))); } } public async ValueTask Read(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { - var file = context.State.Registry["stdin"].Read(); + var file = context.State.Registry["_IO_input"].Read(); var args = context.Arguments.ToArray(); context.Return(); @@ -200,7 +200,7 @@ public ValueTask Type(LuaFunctionExecutionContext context, CancellationToke public async ValueTask Write(LuaFunctionExecutionContext context, CancellationToken cancellationToken) { - var file = context.State.Registry["stdout"].Read(); + var file = context.State.Registry["_IO_output"].Read(); var resultCount = await IOHelper.WriteAsync(file, "write", context, cancellationToken); return resultCount; } diff --git a/src/Lua/Standard/OpenLibsExtensions.cs b/src/Lua/Standard/OpenLibsExtensions.cs index bbc00eb3..03d93f48 100644 --- a/src/Lua/Standard/OpenLibsExtensions.cs +++ b/src/Lua/Standard/OpenLibsExtensions.cs @@ -51,9 +51,8 @@ public static void OpenIOLibrary(this LuaState state) var stdin = new LuaValue(new FileHandle(LuaFileOpenMode.Read, ConsoleHelper.OpenStandardInput())); var stdout = new LuaValue(new FileHandle(LuaFileOpenMode.Write, ConsoleHelper.OpenStandardOutput())); var stderr = new LuaValue(new FileHandle(LuaFileOpenMode.Write, ConsoleHelper.OpenStandardError())); - registry["stdin"] = stdin; - registry["stdout"] = stdout; - registry["stderr"] = stderr; + registry["_IO_input"] = stdin; + registry["_IO_output"] = stdout; io["stdin"] = stdin; io["stdout"] = stdout; io["stderr"] = stderr; From 8104c0ba7fddb03851c047c5e6711f8c599b2783 Mon Sep 17 00:00:00 2001 From: Akeit0 <90429982+Akeit0@users.noreply.github.com> Date: Thu, 22 May 2025 09:37:12 +0900 Subject: [PATCH 33/33] fix: flush was not called on close --- src/Lua/IO/ILuaFileSystem.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Lua/IO/ILuaFileSystem.cs b/src/Lua/IO/ILuaFileSystem.cs index 1bf07739..de2a5477 100644 --- a/src/Lua/IO/ILuaFileSystem.cs +++ b/src/Lua/IO/ILuaFileSystem.cs @@ -73,7 +73,7 @@ public ValueTask ReadFileContentAsync(string path, CancellationT public ILuaIOStream Open(string path, LuaFileOpenMode luaMode) { var (mode, access) = GetFileMode(luaMode); - + if (luaMode == LuaFileOpenMode.ReadAppend) { var s = new LuaIOStreamWrapper(luaMode, File.Open(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite | FileShare.Delete)); @@ -82,7 +82,6 @@ public ILuaIOStream Open(string path, LuaFileOpenMode luaMode) } return new LuaIOStreamWrapper(luaMode, File.Open(path, mode, access, FileShare.ReadWrite | FileShare.Delete)); - } public void Rename(string oldName, string newName) @@ -218,6 +217,7 @@ void ThrowIfNotWritable() public void Dispose() { + if (innerStream.CanWrite) innerStream.Flush(); innerStream.Dispose(); reader?.Dispose(); }