TableLibrary.cs 7.5 KB

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