FileHandle.cs 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213
  1. using Lua.IO;
  2. using Lua.Runtime;
  3. using Lua.Standard.Internal;
  4. namespace Lua.Standard;
  5. // TODO: optimize (remove StreamReader/Writer)
  6. public class FileHandle : ILuaUserData
  7. {
  8. public static readonly LuaFunction IndexMetamethod = new("index", (context, ct) =>
  9. {
  10. context.GetArgument<FileHandle>(0);
  11. var key = context.GetArgument(1);
  12. if (key.TryRead<string>(out var name))
  13. {
  14. return new(context.Return(name switch
  15. {
  16. "close" => CloseFunction!,
  17. "flush" => FlushFunction!,
  18. "lines" => LinesFunction!,
  19. "read" => ReadFunction!,
  20. "seek" => SeekFunction!,
  21. "setvbuf" => SetVBufFunction!,
  22. "write" => WriteFunction!,
  23. _ => LuaValue.Nil,
  24. }));
  25. }
  26. else
  27. {
  28. return new(context.Return(LuaValue.Nil));
  29. }
  30. });
  31. ILuaIOStream stream;
  32. bool isClosed;
  33. public bool IsClosed => Volatile.Read(ref isClosed);
  34. LuaTable? ILuaUserData.Metatable { get => fileHandleMetatable; set => fileHandleMetatable = value; }
  35. static LuaTable? fileHandleMetatable;
  36. static FileHandle()
  37. {
  38. fileHandleMetatable = new LuaTable(0, 1);
  39. fileHandleMetatable[Metamethods.Index] = IndexMetamethod;
  40. }
  41. public FileHandle(LuaFileOpenMode mode, Stream stream) : this(new LuaIOStreamWrapper(mode,stream)) { }
  42. public FileHandle(ILuaIOStream stream)
  43. {
  44. this.stream = stream;
  45. }
  46. public ValueTask<string?> ReadLineAsync(CancellationToken cancellationToken)
  47. {
  48. return stream.ReadLineAsync(cancellationToken);
  49. }
  50. public ValueTask<string> ReadToEndAsync(CancellationToken cancellationToken)
  51. {
  52. return stream.ReadToEndAsync(cancellationToken);
  53. }
  54. public ValueTask<string?> ReadStringAsync(int count, CancellationToken cancellationToken)
  55. {
  56. return stream.ReadStringAsync(count, cancellationToken);
  57. }
  58. public ValueTask WriteAsync(ReadOnlyMemory<char> buffer, CancellationToken cancellationToken)
  59. {
  60. return stream.WriteAsync(buffer, cancellationToken);
  61. }
  62. public long Seek(string whence, long offset) =>
  63. whence switch
  64. {
  65. "set" => stream.Seek(offset, SeekOrigin.Begin),
  66. "cur" => stream.Seek(offset, SeekOrigin.Current),
  67. "end" => stream.Seek(offset, SeekOrigin.End),
  68. _ => throw new ArgumentException($"Invalid option '{whence}'")
  69. };
  70. public ValueTask FlushAsync(CancellationToken cancellationToken)
  71. {
  72. return stream.FlushAsync(cancellationToken);
  73. }
  74. public void SetVBuf(string mode, int size)
  75. {
  76. var bufferingMode = mode switch
  77. {
  78. "no" => LuaFileBufferingMode.NoBuffering,
  79. "full" => LuaFileBufferingMode.FullBuffering,
  80. "line" => LuaFileBufferingMode.LineBuffering,
  81. _ => throw new ArgumentException($"Invalid option '{mode}'")
  82. };
  83. stream.SetVBuf(bufferingMode, size);
  84. }
  85. public void Close()
  86. {
  87. if (isClosed) throw new ObjectDisposedException(nameof(FileHandle));
  88. Volatile.Write(ref isClosed, true);
  89. stream.Dispose();
  90. stream = null!;
  91. }
  92. static readonly LuaFunction CloseFunction = new("close", (context, cancellationToken) =>
  93. {
  94. var file = context.GetArgument<FileHandle>(0);
  95. try
  96. {
  97. file.Close();
  98. return new(context.Return(true));
  99. }
  100. catch (IOException ex)
  101. {
  102. return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult));
  103. }
  104. });
  105. static readonly LuaFunction FlushFunction = new("flush", async (context, cancellationToken) =>
  106. {
  107. var file = context.GetArgument<FileHandle>(0);
  108. try
  109. {
  110. await file.FlushAsync(cancellationToken);
  111. return context.Return(true);
  112. }
  113. catch (IOException ex)
  114. {
  115. return (context.Return(LuaValue.Nil, ex.Message, ex.HResult));
  116. }
  117. });
  118. static readonly LuaFunction LinesFunction = new("lines", (context, cancellationToken) =>
  119. {
  120. var file = context.GetArgument<FileHandle>(0);
  121. var format = context.HasArgument(1)
  122. ? context.Arguments[1]
  123. : "*l";
  124. return new(context.Return(new CSharpClosure("iterator", [new(file), format], static async (context, cancellationToken) =>
  125. {
  126. var upValues = context.GetCsClosure()!.UpValues.AsMemory();
  127. var file = upValues.Span[0].Read<FileHandle>();
  128. context.Return();
  129. var resultCount = await IOHelper.ReadAsync(context.Thread, file, "lines", 0, upValues[1..], true, cancellationToken);
  130. return resultCount;
  131. })));
  132. });
  133. static readonly LuaFunction ReadFunction = new("read", async (context, cancellationToken) =>
  134. {
  135. var file = context.GetArgument<FileHandle>(0);
  136. var args = context.Arguments[1..].ToArray();
  137. context.Return();
  138. var resultCount = await IOHelper.ReadAsync(context.Thread, file, "read", 1, args, false, cancellationToken);
  139. return resultCount;
  140. });
  141. static readonly LuaFunction SeekFunction = new("seek", (context, cancellationToken) =>
  142. {
  143. var file = context.GetArgument<FileHandle>(0);
  144. var whence = context.HasArgument(1)
  145. ? context.GetArgument<string>(1)
  146. : "cur";
  147. var offset = context.HasArgument(2)
  148. ? context.GetArgument<int>(2)
  149. : 0;
  150. if (whence is not ("set" or "cur" or "end"))
  151. {
  152. throw new LuaRuntimeException(context.Thread, $"bad argument #2 to 'seek' (invalid option '{whence}')");
  153. }
  154. try
  155. {
  156. return new(context.Return(file.Seek(whence, (long)offset)));
  157. }
  158. catch (IOException ex)
  159. {
  160. return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult));
  161. }
  162. });
  163. static readonly LuaFunction SetVBufFunction = new("setvbuf", (context, cancellationToken) =>
  164. {
  165. var file = context.GetArgument<FileHandle>(0);
  166. var mode = context.GetArgument<string>(1);
  167. var size = context.HasArgument(2)
  168. ? context.GetArgument<int>(2)
  169. : -1;
  170. file.SetVBuf(mode, size);
  171. return new(context.Return(true));
  172. });
  173. static readonly LuaFunction WriteFunction = new("write", async (context, cancellationToken) =>
  174. {
  175. var file = context.GetArgument<FileHandle>(0);
  176. var resultCount = await IOHelper.WriteAsync(file, "write", context with { ArgumentCount = context.ArgumentCount - 1 }, cancellationToken);
  177. return resultCount;
  178. });
  179. }