FileHandle.cs 7.4 KB

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