ModuleLibrary.cs 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. using Lua.Runtime;
  2. namespace Lua.Standard;
  3. public sealed class ModuleLibrary
  4. {
  5. public static readonly ModuleLibrary Instance = new();
  6. internal const string LoadedKeyForRegistry = "_LOADED";
  7. internal const string PreloadKeyForRegistry = "_PRELOAD";
  8. public ModuleLibrary()
  9. {
  10. RequireFunction = new("require", Require);
  11. SearchPathFunction = new("package.searchpath", SearchPath);
  12. }
  13. public readonly LuaFunction RequireFunction;
  14. public readonly LuaFunction SearchPathFunction;
  15. public async ValueTask<int> Require(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  16. {
  17. var arg0 = context.GetArgument<string>(0);
  18. var loaded = context.State.LoadedModules;
  19. if (!loaded.TryGetValue(arg0, out var loadedTable))
  20. {
  21. var loader = await FindLoader(context.Access, arg0, cancellationToken);
  22. await context.Access.RunAsync(loader, 0, context.ReturnFrameBase, cancellationToken);
  23. loadedTable = context.Thread.Stack.Get(context.ReturnFrameBase);
  24. loaded[arg0] = loadedTable;
  25. }
  26. return context.Return(loadedTable);
  27. }
  28. internal static async ValueTask<string?> FindFile(LuaThreadAccess access, string name, string pName, string dirSeparator)
  29. {
  30. var thread = access.Thread;
  31. var state = thread.State;
  32. var package = state.Environment["package"];
  33. var p = await access.GetTable(package, pName);
  34. if (!p.TryReadString(out var path))
  35. {
  36. throw new LuaRuntimeException(thread, ($"package.{pName} must be a string"));
  37. }
  38. return SearchPath(state, name, path, ".", dirSeparator);
  39. }
  40. public ValueTask<int> SearchPath(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  41. {
  42. var name = context.GetArgument<string>(0);
  43. var path = context.GetArgument<string>(1);
  44. var separator = context.GetArgument<string>(2);
  45. var dirSeparator = context.GetArgument<string>(3);
  46. var fileName = SearchPath(context.State, name, path, separator, dirSeparator);
  47. return new(context.Return(fileName ?? LuaValue.Nil));
  48. }
  49. internal static string? SearchPath(LuaState state, string name, string path, string separator, string dirSeparator)
  50. {
  51. if (separator != "")
  52. {
  53. name = name.Replace(separator, dirSeparator);
  54. }
  55. var pathSpan = path.AsSpan();
  56. var nextIndex = pathSpan.IndexOf(';');
  57. if (nextIndex == -1) nextIndex = pathSpan.Length;
  58. do
  59. {
  60. path = pathSpan[..nextIndex].ToString();
  61. var fileName = path.Replace("?", name);
  62. if (state.FileSystem.IsReadable(fileName))
  63. {
  64. return fileName;
  65. }
  66. if (pathSpan.Length <= nextIndex) break;
  67. pathSpan = pathSpan[(nextIndex + 1)..];
  68. nextIndex = pathSpan.IndexOf(';');
  69. if (nextIndex == -1) nextIndex = pathSpan.Length;
  70. } while (nextIndex != -1);
  71. return null;
  72. }
  73. internal static async ValueTask<LuaFunction> FindLoader(LuaThreadAccess access, string name, CancellationToken cancellationToken)
  74. {
  75. var state = access.State;
  76. var package = state.Environment["package"].Read<LuaTable>();
  77. var searchers = package["searchers"].Read<LuaTable>();
  78. for (int i = 0; i < searchers.GetArraySpan().Length; i++)
  79. {
  80. var searcher = searchers.GetArraySpan()[i];
  81. if (searcher.Type == LuaValueType.Nil) continue;
  82. var loader = searcher;
  83. var top = access.Stack.Count;
  84. access.Stack.Push(loader);
  85. access.Stack.Push(name);
  86. var resultCount = await access.Call(top, top, cancellationToken);
  87. if (0 < resultCount)
  88. {
  89. var result = access.Stack.Get(top);
  90. if (result.Type == LuaValueType.Function)
  91. {
  92. access.Stack.SetTop(top);
  93. return result.Read<LuaFunction>();
  94. }
  95. }
  96. access.Stack.SetTop(top);
  97. }
  98. throw new LuaRuntimeException(access.Thread, ($"Module '{name}' not found"));
  99. }
  100. public ValueTask<int> SearcherPreload(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  101. {
  102. var name = context.GetArgument<string>(0);
  103. var preload = context.State.PreloadModules[name];
  104. if (preload == LuaValue.Nil)
  105. {
  106. return new(context.Return());
  107. }
  108. return new(context.Return(preload));
  109. }
  110. public async ValueTask<int> SearcherLua(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  111. {
  112. var name = context.GetArgument<string>(0);
  113. var fileName = await FindFile(context.Access, name, "path", context.State.FileSystem.DirectorySeparator);
  114. if (fileName == null)
  115. {
  116. return (context.Return(LuaValue.Nil));
  117. }
  118. return context.Return(await context.State.LoadFileAsync(fileName, "bt", null, cancellationToken));
  119. }
  120. }