ModuleLibrary.cs 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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.GlobalState.LoadedModules;
  19. if (!loaded.TryGetValue(arg0, out var loadedTable))
  20. {
  21. LuaFunction loader;
  22. var moduleLoader = context.GlobalState.ModuleLoader;
  23. if (moduleLoader != null && moduleLoader.Exists(arg0))
  24. {
  25. var module = await moduleLoader.LoadAsync(arg0, cancellationToken);
  26. loader = module.Type == LuaModuleType.Bytes
  27. ? context.State.Load(module.ReadBytes(), module.Name)
  28. : context.State.Load(module.ReadText(), module.Name);
  29. }
  30. else
  31. {
  32. loader = await FindLoader(context.State, arg0, cancellationToken);
  33. }
  34. await context.State.RunAsync(loader, 0, context.ReturnFrameBase, cancellationToken);
  35. loadedTable = context.State.Stack.Get(context.ReturnFrameBase);
  36. loaded[arg0] = loadedTable;
  37. }
  38. return context.Return(loadedTable);
  39. }
  40. internal static async ValueTask<string?> FindFile(LuaState state, string name, string pName, string dirSeparator)
  41. {
  42. var globalState = state.GlobalState;
  43. var package = globalState.Environment["package"];
  44. var p = await state.GetTable(package, pName);
  45. if (!p.TryReadString(out var path))
  46. {
  47. throw new LuaRuntimeException(state, $"package.{pName} must be a string");
  48. }
  49. return SearchPath(state, name, path, ".", dirSeparator);
  50. }
  51. public ValueTask<int> SearchPath(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  52. {
  53. var name = context.GetArgument<string>(0);
  54. var path = context.GetArgument<string>(1);
  55. var separator = context.GetArgument<string>(2);
  56. var dirSeparator = context.GetArgument<string>(3);
  57. var fileName = SearchPath(context.State, name, path, separator, dirSeparator);
  58. return new(context.Return(fileName ?? LuaValue.Nil));
  59. }
  60. internal static string? SearchPath(LuaState state, string name, string path, string separator, string dirSeparator)
  61. {
  62. if (separator != "")
  63. {
  64. name = name.Replace(separator, dirSeparator);
  65. }
  66. var pathSpan = path.AsSpan();
  67. var nextIndex = pathSpan.IndexOf(';');
  68. if (nextIndex == -1)
  69. {
  70. nextIndex = pathSpan.Length;
  71. }
  72. do
  73. {
  74. path = pathSpan[..nextIndex].ToString();
  75. var fileName = path.Replace("?", name);
  76. if (state.GlobalState.Platform.FileSystem.IsReadable(fileName))
  77. {
  78. return fileName;
  79. }
  80. if (pathSpan.Length <= nextIndex)
  81. {
  82. break;
  83. }
  84. pathSpan = pathSpan[(nextIndex + 1)..];
  85. nextIndex = pathSpan.IndexOf(';');
  86. if (nextIndex == -1)
  87. {
  88. nextIndex = pathSpan.Length;
  89. }
  90. } while (nextIndex != -1);
  91. return null;
  92. }
  93. internal static async ValueTask<LuaFunction> FindLoader(LuaState state, string name, CancellationToken cancellationToken)
  94. {
  95. var package = state.GlobalState.Environment["package"].Read<LuaTable>();
  96. var searchers = package["searchers"].Read<LuaTable>();
  97. for (var i = 0; i < searchers.GetArraySpan().Length; i++)
  98. {
  99. var searcher = searchers.GetArraySpan()[i];
  100. if (searcher.Type == LuaValueType.Nil)
  101. {
  102. continue;
  103. }
  104. var loader = searcher;
  105. var top = state.Stack.Count;
  106. state.Stack.Push(loader);
  107. state.Stack.Push(name);
  108. var resultCount = await state.Call(top, top, cancellationToken);
  109. if (0 < resultCount)
  110. {
  111. var result = state.Stack.Get(top);
  112. if (result.Type == LuaValueType.Function)
  113. {
  114. state.Stack.SetTop(top);
  115. return result.Read<LuaFunction>();
  116. }
  117. }
  118. state.Stack.SetTop(top);
  119. }
  120. throw new LuaRuntimeException(state, $"Module '{name}' not found");
  121. }
  122. public ValueTask<int> SearcherPreload(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  123. {
  124. var name = context.GetArgument<string>(0);
  125. var preload = context.GlobalState.PreloadModules[name];
  126. if (preload == LuaValue.Nil)
  127. {
  128. return new(context.Return());
  129. }
  130. return new(context.Return(preload));
  131. }
  132. public async ValueTask<int> SearcherLua(LuaFunctionExecutionContext context, CancellationToken cancellationToken)
  133. {
  134. var name = context.GetArgument<string>(0);
  135. var fileName = await FindFile(context.State, name, "path", context.GlobalState.Platform.FileSystem.DirectorySeparator);
  136. if (fileName == null)
  137. {
  138. return context.Return(LuaValue.Nil);
  139. }
  140. return context.Return(await context.State.LoadFileAsync(fileName, "bt", null, cancellationToken));
  141. }
  142. }