瀏覽代碼

'require' returns where module was found

The function 'require' returns the *loader data* as a second result.
For file searchers, this data is the path where they found the module.
Roberto Ierusalimschy 6 年之前
父節點
當前提交
ed2872cd3b
共有 4 個文件被更改,包括 66 次插入34 次删除
  1. 17 6
      loadlib.c
  2. 2 2
      lundump.c
  3. 27 11
      manual/manual.of
  4. 20 15
      testes/attrib.lua

+ 17 - 6
loadlib.c

@@ -576,9 +576,14 @@ static int searcher_Croot (lua_State *L) {
 static int searcher_preload (lua_State *L) {
 static int searcher_preload (lua_State *L) {
   const char *name = luaL_checkstring(L, 1);
   const char *name = luaL_checkstring(L, 1);
   lua_getfield(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
   lua_getfield(L, LUA_REGISTRYINDEX, LUA_PRELOAD_TABLE);
-  if (lua_getfield(L, -1, name) == LUA_TNIL)  /* not found? */
+  if (lua_getfield(L, -1, name) == LUA_TNIL) {  /* not found? */
     lua_pushfstring(L, "\n\tno field package.preload['%s']", name);
     lua_pushfstring(L, "\n\tno field package.preload['%s']", name);
-  return 1;
+    return 1;
+  }
+  else {
+    lua_pushliteral(L, ":preload:");
+    return 2;
+  }
 }
 }
 
 
 
 
@@ -620,17 +625,23 @@ static int ll_require (lua_State *L) {
   /* else must load package */
   /* else must load package */
   lua_pop(L, 1);  /* remove 'getfield' result */
   lua_pop(L, 1);  /* remove 'getfield' result */
   findloader(L, name);
   findloader(L, name);
-  lua_pushstring(L, name);  /* pass name as argument to module loader */
-  lua_insert(L, -2);  /* name is 1st argument (before search data) */
+  lua_rotate(L, -2, 1);  /* function <-> loader data */
+  lua_pushvalue(L, 1);  /* name is 1st argument to module loader */
+  lua_pushvalue(L, -3);  /* loader data is 2nd argument */
+  /* stack: ...; loader data; loader function; mod. name; loader data */
   lua_call(L, 2, 1);  /* run loader to load module */
   lua_call(L, 2, 1);  /* run loader to load module */
+  /* stack: ...; loader data; result from loader */
   if (!lua_isnil(L, -1))  /* non-nil return? */
   if (!lua_isnil(L, -1))  /* non-nil return? */
     lua_setfield(L, 2, name);  /* LOADED[name] = returned value */
     lua_setfield(L, 2, name);  /* LOADED[name] = returned value */
+  else
+    lua_pop(L, 1);  /* pop nil */
   if (lua_getfield(L, 2, name) == LUA_TNIL) {   /* module set no value? */
   if (lua_getfield(L, 2, name) == LUA_TNIL) {   /* module set no value? */
     lua_pushboolean(L, 1);  /* use true as result */
     lua_pushboolean(L, 1);  /* use true as result */
-    lua_pushvalue(L, -1);  /* extra copy to be returned */
+    lua_copy(L, -1, -2);  /* replace loader result */
     lua_setfield(L, 2, name);  /* LOADED[name] = true */
     lua_setfield(L, 2, name);  /* LOADED[name] = true */
   }
   }
-  return 1;
+  lua_rotate(L, -2, 1);  /* loader data <-> module result  */
+  return 2;  /* return module result and loader data */
 }
 }
 
 
 /* }====================================================== */
 /* }====================================================== */

+ 2 - 2
lundump.c

@@ -271,8 +271,8 @@ static void fchecksize (LoadState *S, size_t size, const char *tname) {
 #define checksize(S,t)	fchecksize(S,sizeof(t),#t)
 #define checksize(S,t)	fchecksize(S,sizeof(t),#t)
 
 
 static void checkHeader (LoadState *S) {
 static void checkHeader (LoadState *S) {
-  /* 1st char already checked */
-  checkliteral(S, LUA_SIGNATURE + 1, "not a binary chunk");
+  /* skip 1st char (already read and checked) */
+  checkliteral(S, &LUA_SIGNATURE[1], "not a binary chunk");
   if (LoadInt(S) != LUAC_VERSION)
   if (LoadInt(S) != LUAC_VERSION)
     error(S, "version mismatch");
     error(S, "version mismatch");
   if (LoadByte(S) != LUAC_FORMAT)
   if (LoadByte(S) != LUAC_FORMAT)

+ 27 - 11
manual/manual.of

@@ -6408,11 +6408,15 @@ The function starts by looking into the @Lid{package.loaded} table
 to determine whether @id{modname} is already loaded.
 to determine whether @id{modname} is already loaded.
 If it is, then @id{require} returns the value stored
 If it is, then @id{require} returns the value stored
 at @T{package.loaded[modname]}.
 at @T{package.loaded[modname]}.
+(The absence of a second result in this case
+signals that this call did not have to load the module.)
 Otherwise, it tries to find a @emph{loader} for the module.
 Otherwise, it tries to find a @emph{loader} for the module.
 
 
 To find a loader,
 To find a loader,
-@id{require} is guided by the @Lid{package.searchers} sequence.
-By changing this sequence,
+@id{require} is guided by the table @Lid{package.searchers}.
+Each item in this table is a search function,
+that searches for the module in a particular way.
+By changing this table,
 we can change how @id{require} looks for a module.
 we can change how @id{require} looks for a module.
 The following explanation is based on the default configuration
 The following explanation is based on the default configuration
 for @Lid{package.searchers}.
 for @Lid{package.searchers}.
@@ -6429,9 +6433,14 @@ it tries an @emph{all-in-one} loader @seeF{package.searchers}.
 
 
 Once a loader is found,
 Once a loader is found,
 @id{require} calls the loader with two arguments:
 @id{require} calls the loader with two arguments:
-@id{modname} and an extra value dependent on how it got the loader.
-(If the loader came from a file,
-this extra value is the file name.)
+@id{modname} and an extra value,
+a @emph{loader data},
+also returned by the searcher.
+The loader data can be any value useful to the module;
+for the default searchers,
+it indicates where the loader was found.
+(For instance, if the loader came from a file,
+this extra value is the file path.)
 If the loader returns any non-nil value,
 If the loader returns any non-nil value,
 @id{require} assigns the returned value to @T{package.loaded[modname]}.
 @id{require} assigns the returned value to @T{package.loaded[modname]}.
 If the loader does not return a non-nil value and
 If the loader does not return a non-nil value and
@@ -6439,6 +6448,9 @@ has not assigned any value to @T{package.loaded[modname]},
 then @id{require} assigns @Rw{true} to this entry.
 then @id{require} assigns @Rw{true} to this entry.
 In any case, @id{require} returns the
 In any case, @id{require} returns the
 final value of @T{package.loaded[modname]}.
 final value of @T{package.loaded[modname]}.
+Besides that value, @id{require} also returns as a second result
+the loader data returned by the searcher,
+which indicates how @id{require} found the module.
 
 
 If there is any error loading or running the module,
 If there is any error loading or running the module,
 or if it cannot find any loader for the module,
 or if it cannot find any loader for the module,
@@ -6558,16 +6570,20 @@ table used by @Lid{require}.
 
 
 @LibEntry{package.searchers|
 @LibEntry{package.searchers|
 
 
-A table used by @Lid{require} to control how to load modules.
+A table used by @Lid{require} to control how to find modules.
 
 
 Each entry in this table is a @def{searcher function}.
 Each entry in this table is a @def{searcher function}.
 When looking for a module,
 When looking for a module,
 @Lid{require} calls each of these searchers in ascending order,
 @Lid{require} calls each of these searchers in ascending order,
 with the module name (the argument given to @Lid{require}) as its
 with the module name (the argument given to @Lid{require}) as its
 sole argument.
 sole argument.
-The function can return another function (the module @def{loader})
-plus an extra value that will be passed to that loader,
-or a string explaining why it did not find that module
+If the searcher finds the module,
+it returns another function, the module @def{loader},
+plus an extra value, a @emph{loader data},
+that will be passed to that loader and
+returned as a second result by @Lid{require}.
+If it cannot find the module,
+it returns a string explaining why
 (or @nil if it has nothing to say).
 (or @nil if it has nothing to say).
 
 
 Lua initializes this table with four searcher functions.
 Lua initializes this table with four searcher functions.
@@ -6617,9 +6633,9 @@ into one single library,
 with each submodule keeping its original open function.
 with each submodule keeping its original open function.
 
 
 All searchers except the first one (preload) return as the extra value
 All searchers except the first one (preload) return as the extra value
-the file name where the module was found,
+the file path where the module was found,
 as returned by @Lid{package.searchpath}.
 as returned by @Lid{package.searchpath}.
-The first searcher returns no extra value.
+The first searcher always returns the string @St{:preload:}.
 
 
 }
 }
 
 

+ 20 - 15
testes/attrib.lua

@@ -122,12 +122,13 @@ local oldpath = package.path
 
 
 package.path = string.gsub("D/?.lua;D/?.lc;D/?;D/??x?;D/L", "D/", DIR)
 package.path = string.gsub("D/?.lua;D/?.lc;D/?;D/??x?;D/L", "D/", DIR)
 
 
-local try = function (p, n, r)
+local try = function (p, n, r, ext)
   NAME = nil
   NAME = nil
-  local rr = require(p)
+  local rr, x = require(p)
   assert(NAME == n)
   assert(NAME == n)
   assert(REQUIRED == p)
   assert(REQUIRED == p)
   assert(rr == r)
   assert(rr == r)
+  assert(ext == x)
 end
 end
 
 
 a = require"names"
 a = require"names"
@@ -143,27 +144,27 @@ assert(package.searchpath("C", package.path) == D"C.lua")
 assert(require"C" == 25)
 assert(require"C" == 25)
 assert(require"C" == 25)
 assert(require"C" == 25)
 AA = nil
 AA = nil
-try('B', 'B.lua', true)
+try('B', 'B.lua', true, "libs/B.lua")
 assert(package.loaded.B)
 assert(package.loaded.B)
 assert(require"B" == true)
 assert(require"B" == true)
 assert(package.loaded.A)
 assert(package.loaded.A)
 assert(require"C" == 25)
 assert(require"C" == 25)
 package.loaded.A = nil
 package.loaded.A = nil
-try('B', nil, true)   -- should not reload package
-try('A', 'A.lua', true)
+try('B', nil, true, nil)   -- should not reload package
+try('A', 'A.lua', true, "libs/A.lua")
 package.loaded.A = nil
 package.loaded.A = nil
 os.remove(D'A.lua')
 os.remove(D'A.lua')
 AA = {}
 AA = {}
-try('A', 'A.lc', AA)  -- now must find second option
+try('A', 'A.lc', AA, "libs/A.lc")  -- now must find second option
 assert(package.searchpath("A", package.path) == D"A.lc")
 assert(package.searchpath("A", package.path) == D"A.lc")
 assert(require("A") == AA)
 assert(require("A") == AA)
 AA = false
 AA = false
-try('K', 'L', false)     -- default option
-try('K', 'L', false)     -- default option (should reload it)
+try('K', 'L', false, "libs/L")     -- default option
+try('K', 'L', false, "libs/L")     -- default option (should reload it)
 assert(rawget(_G, "_REQUIREDNAME") == nil)
 assert(rawget(_G, "_REQUIREDNAME") == nil)
 
 
 AA = "x"
 AA = "x"
-try("X", "XXxX", AA)
+try("X", "XXxX", AA, "libs/XXxX")
 
 
 
 
 removefiles(files)
 removefiles(files)
@@ -183,14 +184,16 @@ files = {
 createfiles(files, "_ENV = {}\n", "\nreturn _ENV\n")
 createfiles(files, "_ENV = {}\n", "\nreturn _ENV\n")
 AA = 0
 AA = 0
 
 
-local m = assert(require"P1")
+local m, ext = assert(require"P1")
+assert(ext == "libs/P1/init.lua")
 assert(AA == 0 and m.AA == 10)
 assert(AA == 0 and m.AA == 10)
 assert(require"P1" == m)
 assert(require"P1" == m)
 assert(require"P1" == m)
 assert(require"P1" == m)
 
 
 assert(package.searchpath("P1.xuxu", package.path) == D"P1/xuxu.lua")
 assert(package.searchpath("P1.xuxu", package.path) == D"P1/xuxu.lua")
-m.xuxu = assert(require"P1.xuxu")
+m.xuxu, ext = assert(require"P1.xuxu")
 assert(AA == 0 and m.xuxu.AA == 20)
 assert(AA == 0 and m.xuxu.AA == 20)
+assert(ext == "libs/P1/xuxu.lua")
 assert(require"P1.xuxu" == m.xuxu)
 assert(require"P1.xuxu" == m.xuxu)
 assert(require"P1.xuxu" == m.xuxu)
 assert(require"P1.xuxu" == m.xuxu)
 assert(require"P1" == m and m.AA == 10)
 assert(require"P1" == m and m.AA == 10)
@@ -267,15 +270,17 @@ else
 
 
   -- test C modules with prefixes in names
   -- test C modules with prefixes in names
   package.cpath = DC"?"
   package.cpath = DC"?"
-  local lib2 = require"lib2-v2"
+  local lib2, ext = require"lib2-v2"
+  assert(string.find(ext, "libs/lib2-v2", 1, true))
   -- check correct access to global environment and correct
   -- check correct access to global environment and correct
   -- parameters
   -- parameters
   assert(_ENV.x == "lib2-v2" and _ENV.y == DC"lib2-v2")
   assert(_ENV.x == "lib2-v2" and _ENV.y == DC"lib2-v2")
   assert(lib2.id("x") == "x")
   assert(lib2.id("x") == "x")
 
 
   -- test C submodules
   -- test C submodules
-  local fs = require"lib1.sub"
+  local fs, ext = require"lib1.sub"
   assert(_ENV.x == "lib1.sub" and _ENV.y == DC"lib1")
   assert(_ENV.x == "lib1.sub" and _ENV.y == DC"lib1")
+  assert(string.find(ext, "libs/lib1", 1, true))
   assert(fs.id(45) == 45)
   assert(fs.id(45) == 45)
 end
 end
 
 
@@ -293,10 +298,10 @@ do
     return _ENV
     return _ENV
   end
   end
 
 
-  local pl = require"pl"
+  local pl, ext = require"pl"
   assert(require"pl" == pl)
   assert(require"pl" == pl)
   assert(pl.xuxu(10) == 30)
   assert(pl.xuxu(10) == 30)
-  assert(pl[1] == "pl" and pl[2] == nil)
+  assert(pl[1] == "pl" and pl[2] == ":preload:" and ext == ":preload:")
 
 
   package = p
   package = p
   assert(type(package.path) == "string")
   assert(type(package.path) == "string")