TableLibrary.cs 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229
  1. using System.Runtime.CompilerServices;
  2. using System.Text;
  3. using Lua.Internal;
  4. using Lua.Runtime;
  5. namespace Lua.Standard;
  6. public sealed class TableLibrary
  7. {
  8. public static readonly TableLibrary Instance = new();
  9. public TableLibrary()
  10. {
  11. Functions = [
  12. new("concat", Concat),
  13. new("insert", Insert),
  14. new("pack", Pack),
  15. new("remove", Remove),
  16. new("sort", Sort),
  17. new("unpack", Unpack),
  18. ];
  19. }
  20. public readonly LuaFunction[] Functions;
  21. // TODO: optimize
  22. static readonly Chunk defaultComparer = new()
  23. {
  24. Name = "comp",
  25. Functions = [],
  26. Constants = [],
  27. Instructions = [
  28. Instruction.Le(1, 0, 1),
  29. Instruction.LoadBool(2, 1, 1),
  30. Instruction.LoadBool(2, 0, 0),
  31. Instruction.Return(2, 2),
  32. ],
  33. SourcePositions = [
  34. default, default, default, default,
  35. ],
  36. ParameterCount = 2,
  37. UpValues = [],
  38. };
  39. public ValueTask<int> Concat(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  40. {
  41. var arg0 = context.GetArgument<LuaTable>(0);
  42. var arg1 = context.HasArgument(1)
  43. ? context.GetArgument<string>(1)
  44. : "";
  45. var arg2 = context.HasArgument(2)
  46. ? (long)context.GetArgument<double>(2)
  47. : 1;
  48. var arg3 = context.HasArgument(3)
  49. ? (long)context.GetArgument<double>(3)
  50. : arg0.ArrayLength;
  51. var builder = new ValueStringBuilder(512);
  52. for (long i = arg2; i <= arg3; i++)
  53. {
  54. var value = arg0[i];
  55. if (value.Type is LuaValueType.String)
  56. {
  57. builder.Append(value.Read<string>());
  58. }
  59. else if (value.Type is LuaValueType.Number)
  60. {
  61. builder.Append(value.Read<double>().ToString());
  62. }
  63. else
  64. {
  65. throw new LuaRuntimeException(context.State.GetTraceback(), $"invalid value ({value.Type}) at index {i} in table for 'concat'");
  66. }
  67. if (i != arg3) builder.Append(arg1);
  68. }
  69. buffer.Span[0] = builder.ToString();
  70. return new(1);
  71. }
  72. public ValueTask<int> Insert(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  73. {
  74. var table = context.GetArgument<LuaTable>(0);
  75. var value = context.HasArgument(2)
  76. ? context.GetArgument(2)
  77. : context.GetArgument(1);
  78. var pos_arg = context.HasArgument(2)
  79. ? context.GetArgument<double>(1)
  80. : table.ArrayLength + 1;
  81. LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "insert", 2, pos_arg);
  82. var pos = (int)pos_arg;
  83. if (pos <= 0 || pos > table.ArrayLength + 1)
  84. {
  85. throw new LuaRuntimeException(context.State.GetTraceback(), "bad argument #2 to 'insert' (position out of bounds)");
  86. }
  87. table.Insert(pos, value);
  88. return new(0);
  89. }
  90. public ValueTask<int> Pack(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  91. {
  92. var table = new LuaTable(context.ArgumentCount, 1);
  93. var span = context.Arguments;
  94. for (int i = 0; i < span.Length; i++)
  95. {
  96. table[i + 1] = span[i];
  97. }
  98. table["n"] = span.Length;
  99. buffer.Span[0] = table;
  100. return new(1);
  101. }
  102. public ValueTask<int> Remove(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  103. {
  104. var table = context.GetArgument<LuaTable>(0);
  105. var n_arg = context.HasArgument(1)
  106. ? context.GetArgument<double>(1)
  107. : table.ArrayLength;
  108. LuaRuntimeException.ThrowBadArgumentIfNumberIsNotInteger(context.State, "remove", 2, n_arg);
  109. var n = (int)n_arg;
  110. if (n <= 0 || n > table.GetArraySpan().Length)
  111. {
  112. if (!context.HasArgument(1) && n == 0)
  113. {
  114. buffer.Span[0] = LuaValue.Nil;
  115. return new(1);
  116. }
  117. throw new LuaRuntimeException(context.State.GetTraceback(), "bad argument #2 to 'remove' (position out of bounds)");
  118. }
  119. else if (n > table.ArrayLength)
  120. {
  121. buffer.Span[0] = LuaValue.Nil;
  122. return new(1);
  123. }
  124. buffer.Span[0] = table.RemoveAt(n);
  125. return new(1);
  126. }
  127. public async ValueTask<int> Sort(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  128. {
  129. var arg0 = context.GetArgument<LuaTable>(0);
  130. var arg1 = context.HasArgument(1)
  131. ? context.GetArgument<LuaFunction>(1)
  132. : new Closure(context.State, defaultComparer);
  133. await QuickSortAsync(context, arg0.GetArrayMemory(), 0, arg0.ArrayLength - 1, arg1, cancellationToken);
  134. return 0;
  135. }
  136. async ValueTask QuickSortAsync(LuaFunctionExecutionContext context, Memory<LuaValue> memory, int low, int high, LuaFunction comparer, CancellationToken cancellationToken)
  137. {
  138. if (low < high)
  139. {
  140. int pivotIndex = await PartitionAsync(context, memory, low, high, comparer, cancellationToken);
  141. await QuickSortAsync(context, memory, low, pivotIndex - 1, comparer, cancellationToken);
  142. await QuickSortAsync(context, memory, pivotIndex + 1, high, comparer, cancellationToken);
  143. }
  144. }
  145. async ValueTask<int> PartitionAsync(LuaFunctionExecutionContext context, Memory<LuaValue> memory, int low, int high, LuaFunction comparer, CancellationToken cancellationToken)
  146. {
  147. using var methodBuffer = new PooledArray<LuaValue>(1);
  148. var pivot = memory.Span[high];
  149. int i = low - 1;
  150. for (int j = low; j < high; j++)
  151. {
  152. context.State.Push(memory.Span[j]);
  153. context.State.Push(pivot);
  154. await comparer.InvokeAsync(context with
  155. {
  156. ArgumentCount = 2,
  157. FrameBase = context.Thread.Stack.Count - context.ArgumentCount,
  158. }, methodBuffer.AsMemory(), cancellationToken);
  159. if (methodBuffer[0].ToBoolean())
  160. {
  161. i++;
  162. Swap(memory.Span, i, j);
  163. }
  164. }
  165. Swap(memory.Span, i + 1, high);
  166. return i + 1;
  167. }
  168. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  169. void Swap(Span<LuaValue> span, int i, int j)
  170. {
  171. (span[i], span[j]) = (span[j], span[i]);
  172. }
  173. public ValueTask<int> Unpack(LuaFunctionExecutionContext context, Memory<LuaValue> buffer, CancellationToken cancellationToken)
  174. {
  175. var arg0 = context.GetArgument<LuaTable>(0);
  176. var arg1 = context.HasArgument(1)
  177. ? (int)context.GetArgument<double>(1)
  178. : 1;
  179. var arg2 = context.HasArgument(2)
  180. ? (int)context.GetArgument<double>(2)
  181. : arg0.ArrayLength;
  182. var index = 0;
  183. for (int i = arg1; i <= arg2; i++)
  184. {
  185. buffer.Span[index] = arg0[i];
  186. index++;
  187. }
  188. return new(index);
  189. }
  190. }