FileHandle.cs 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. using Lua.Runtime;
  2. using Lua.Standard.Internal;
  3. namespace Lua.Standard;
  4. // TODO: optimize (remove StreamReader/Writer)
  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. Stream stream;
  31. StreamWriter? writer;
  32. StreamReader? reader;
  33. bool isClosed;
  34. public bool IsClosed => Volatile.Read(ref isClosed);
  35. LuaTable? ILuaUserData.Metatable { get => fileHandleMetatable; set => fileHandleMetatable = value; }
  36. static LuaTable? fileHandleMetatable;
  37. static FileHandle()
  38. {
  39. fileHandleMetatable = new LuaTable();
  40. fileHandleMetatable[Metamethods.Index] = IndexMetamethod;
  41. }
  42. public FileHandle(Stream stream)
  43. {
  44. this.stream = stream;
  45. if (stream.CanRead) reader = new StreamReader(stream);
  46. if (stream.CanWrite) writer = new StreamWriter(stream);
  47. }
  48. public string? ReadLine()
  49. {
  50. return reader!.ReadLine();
  51. }
  52. public string ReadToEnd()
  53. {
  54. return reader!.ReadToEnd();
  55. }
  56. public int ReadByte()
  57. {
  58. return stream.ReadByte();
  59. }
  60. public void Write(ReadOnlySpan<char> buffer)
  61. {
  62. writer!.Write(buffer);
  63. }
  64. public long Seek(string whence, long offset)
  65. {
  66. if (whence != null)
  67. {
  68. switch (whence)
  69. {
  70. case "set":
  71. stream.Seek(offset, SeekOrigin.Begin);
  72. break;
  73. case "cur":
  74. stream.Seek(offset, SeekOrigin.Current);
  75. break;
  76. case "end":
  77. stream.Seek(offset, SeekOrigin.End);
  78. break;
  79. default:
  80. throw new ArgumentException($"Invalid option '{whence}'");
  81. }
  82. }
  83. return stream.Position;
  84. }
  85. public void Flush()
  86. {
  87. writer!.Flush();
  88. }
  89. public void SetVBuf(string mode, int size)
  90. {
  91. // Ignore size parameter
  92. if (writer != null)
  93. {
  94. writer.AutoFlush = mode is "no" or "line";
  95. }
  96. }
  97. public void Close()
  98. {
  99. if (isClosed) throw new ObjectDisposedException(nameof(FileHandle));
  100. Volatile.Write(ref isClosed, true);
  101. if (reader != null)
  102. {
  103. reader.Dispose();
  104. }
  105. else
  106. {
  107. stream.Close();
  108. }
  109. }
  110. static readonly LuaFunction CloseFunction = new("close", (context, cancellationToken) =>
  111. {
  112. var file = context.GetArgument<FileHandle>(0);
  113. try
  114. {
  115. file.Close();
  116. return new(context.Return(true));
  117. }
  118. catch (IOException ex)
  119. {
  120. return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult));
  121. }
  122. });
  123. static readonly LuaFunction FlushFunction = new("flush", (context, cancellationToken) =>
  124. {
  125. var file = context.GetArgument<FileHandle>(0);
  126. try
  127. {
  128. file.Flush();
  129. return new(context.Return(true));
  130. }
  131. catch (IOException ex)
  132. {
  133. return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult));
  134. }
  135. });
  136. static readonly LuaFunction LinesFunction = new("lines", (context, cancellationToken) =>
  137. {
  138. var file = context.GetArgument<FileHandle>(0);
  139. var format = context.HasArgument(1)
  140. ? context.Arguments[1]
  141. : "*l";
  142. return new(context.Return(new CSharpClosure("iterator", [new(file), format], static (context, cancellationToken) =>
  143. {
  144. var upValues = context.GetCsClosure()!.UpValues.AsSpan();
  145. var file = upValues[0].Read<FileHandle>();
  146. context.Return();
  147. var resultCount = IOHelper.Read(context.State, file, "lines", 0, upValues[1..], context.Thread.Stack, true);
  148. return new(resultCount);
  149. })));
  150. });
  151. static readonly LuaFunction ReadFunction = new("read", (context, cancellationToken) =>
  152. {
  153. var file = context.GetArgument<FileHandle>(0);
  154. context.Return();
  155. var resultCount = IOHelper.Read(context.State, file, "read", 1, context.Arguments[1..], context.Thread.Stack, false);
  156. return new(resultCount);
  157. });
  158. static readonly LuaFunction SeekFunction = new("seek", (context, cancellationToken) =>
  159. {
  160. var file = context.GetArgument<FileHandle>(0);
  161. var whence = context.HasArgument(1)
  162. ? context.GetArgument<string>(1)
  163. : "cur";
  164. var offset = context.HasArgument(2)
  165. ? context.GetArgument<int>(2)
  166. : 0;
  167. if (whence is not ("set" or "cur" or "end"))
  168. {
  169. throw new LuaRuntimeException(context.State.GetTraceback(), $"bad argument #2 to 'seek' (invalid option '{whence}')");
  170. }
  171. try
  172. {
  173. return new(context.Return(file.Seek(whence, (long)offset)));
  174. }
  175. catch (IOException ex)
  176. {
  177. return new(context.Return(LuaValue.Nil, ex.Message, ex.HResult));
  178. }
  179. });
  180. static readonly LuaFunction SetVBufFunction = new("setvbuf", (context, cancellationToken) =>
  181. {
  182. var file = context.GetArgument<FileHandle>(0);
  183. var mode = context.GetArgument<string>(1);
  184. var size = context.HasArgument(2)
  185. ? context.GetArgument<int>(2)
  186. : -1;
  187. file.SetVBuf(mode, size);
  188. return new(context.Return(true));
  189. });
  190. static readonly LuaFunction WriteFunction = new("write", (context, cancellationToken) =>
  191. {
  192. var file = context.GetArgument<FileHandle>(0);
  193. context.Return();
  194. var resultCount = IOHelper.Write(file, "write", context);
  195. return new(resultCount);
  196. });
  197. }