buldthensnip/src/lua_base.h

200 lines
5.7 KiB
C

/*
This file is part of Iceball.
Iceball is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Iceball is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Iceball. If not, see <http://www.gnu.org/licenses/>.
*/
int icelua_fn_base_loadfile(lua_State *L)
{
int top = icelua_assert_stack(L, 1, 1);
const char *fname = lua_tostring(L, 1);
if(L == lstate_server
? !path_type_server_readable(path_get_type(fname))
: !path_type_client_readable(path_get_type(fname)))
{
return luaL_error(L, "cannot read from there");
}
lua_getglobal(L, "common");
lua_getfield(L, -1, "fetch_block");
lua_remove(L, -2);
lua_pushstring(L, "lua");
lua_pushvalue(L, 1);
lua_call(L, 2, 1);
return 1;
}
int icelua_fn_base_dofile(lua_State *L)
{
int top = icelua_assert_stack(L, 1, 1);
lua_pushcfunction(L, icelua_fn_base_loadfile);
lua_pushvalue(L, 1);
lua_call(L, 1, 1);
// TODO: pcall this
lua_call(L, 0, 0);
return 0;
}
static const int sentinel = 0;
// Attempt to fetch requested module from multiple possible lookup paths
// i.e. "derp.herp" -> {"pkg/derp/herp.lua", "pkg/derp/herp/init.lua", ...}
// Params: mod_name
// Returns chunk if found, errors if not
static int icelua_fn_base_require_helper(lua_State *L)
{
// mod_name = stack[1]
const char *mod_name = luaL_checkstring(L, 1);
// mod_path = stack[2] -- Convert . to /
const char *mod_path = luaL_gsub(L, mod_name, ".", "/");
// paths = stack[3] = ICELUA_REQUIRE_PATH
lua_getfield(L, LUA_REGISTRYINDEX, "ICELUA_REQUIRE_PATH");
int i;
for (i = 1;; i++) {
// path_template = stack[4]
lua_rawgeti(L, 3, i);
if (lua_isnil(L, -1)) {
// End of list
lua_pop(L, 1); // Pop off nil
break;
}
lua_pushcfunction(L, icelua_fn_base_loadfile);
// file_path = stack[6] -- sub mod_path into path_template in place of "?"
luaL_gsub(L, lua_tostring(L, 4), "?", lua_tostring(L, 2));
if(lua_pcall(L, 1, 1, 0) == 0 && !lua_isnil(L, -1)) {
// Loaded! Return loadfile return value
return 1;
} // else, continue looking
lua_pop(L, 2); // Pop off path value and loadfile result
}
// Module not found in any of paths
luaL_error(L, "module " LUA_QS " not found", mod_name);
return 0;
}
int icelua_fn_base_require(lua_State *L)
{
// TODO: Do we want to offer the functionality that the vanilla
// package module provides? i.e. custom loaders, search path, etc.?
// We don't use the builtin one as we'd have to remove a lot of stuff
// to ensure security, so this is easier. If we want full functionality
// though, that may be something to look into. - rakiru
int top = icelua_assert_stack(L, 1, 1);
// mod_name = stack[1]
const char *mod_name = luaL_checkstring(L, 1);
// loaded = stack[2] = LUA_REGISTRYINDEX["_LOADED"]
lua_getfield(L, LUA_REGISTRYINDEX, "_LOADED");
// if loaded == nil
if (lua_isnil(L, 2)) {
lua_pop(L, 2);
// loaded = LUA_REGISTRYINDEX["_LOADED"] = {}
lua_createtable(L, 0, 2);
lua_pushvalue(L, 2); // Dupe table since setfield pops but we want a copy remaining
lua_setfield(L, LUA_REGISTRYINDEX, "_LOADED");
}
// module = stack[3] = loaded[mod_name]
lua_getfield(L, 2, mod_name);
// module?
if (lua_toboolean(L, 3)) {
// Check for loops or previous error while loading
if (lua_touserdata(L, 3) == ((void*)&sentinel)) {
luaL_error(L, "loop or previous error loading module " LUA_QS, mod_name);
}
// Already loaded successfully
return 1;
}
lua_pop(L, 1); // Remove nil module from stack
// Set sentinel to detect loops/error on future require(mod_name)
lua_pushlightuserdata(L, (void*)&sentinel);
lua_setfield(L, 2, mod_name);
// Attempt module path lookup
// module_file = stack[3] = loadfile(mod_name) magic
lua_pushcfunction(L, icelua_fn_base_require_helper);
lua_pushvalue(L, 1);
lua_call(L, 1, 1);
// module = stack[3] = module_file(mod_name) -- module_file is popped
lua_pushvalue(L, 1);
lua_call(L, 1, 1);
// if module == nil then module = true
if (lua_isnil(L, 3)) {
lua_pop(L, 1); // Remove nil from stack
lua_pushboolean(L, 1); // Push true in its place
}
// loaded[mod_name] = module
lua_pushvalue(L, 3); // Dupe value as setfield pops
lua_setfield(L, 2, mod_name);
// return module
return 1;
}
// Install package related stuff to state
void icelua_openpackage(lua_State *L)
{
// We're not providing most of `package` anyway, so just make this a table
// instead of a string as in `package.path` - rakiru
// TODO: Actually, keeping as a hardcoded, internal path allows some
// optimisations, // like doing the lookup server-side to avoid the extra
// latency for files further down the list of paths.
int key = 1;
// Note: Ensure you alter this value if adding/removing paths
lua_createtable(L, 5, 0);
// Most packages will be in pkg/
lua_pushinteger(L, key++);
lua_pushstring(L, "pkg/?.lua");
lua_settable(L, -3);
lua_pushinteger(L, key++);
lua_pushstring(L, "pkg/?/init.lua");
lua_settable(L, -3);
// 3rd party libs can go here without cluttering up the base pkg/ dir
lua_pushinteger(L, key++);
lua_pushstring(L, "pkg/vendor/?.lua");
lua_settable(L, -3);
lua_pushinteger(L, key++);
lua_pushstring(L, "pkg/vendor/?/init.lua");
lua_settable(L, -3);
// On the off-chance you wanted to load code from clsave/svsave or whatever
lua_pushinteger(L, key++);
lua_pushstring(L, "?.lua");
lua_settable(L, -3);
lua_setfield(L, LUA_REGISTRYINDEX, "ICELUA_REQUIRE_PATH");
}