/*
* File: clua.cc
* Created by: dshaligram on Wed Aug 2 12:54:15 2006 UTC
*/
#include "AppHdr.h"
#include "clua.h"
#include "cluautil.h"
#include "dlua.h"
#include "l_libs.h"
#include "files.h"
#include "libutil.h"
#include "state.h"
#include "stuff.h"
#include <algorithm>
#define BUGGY_PCALL_ERROR "667: Malformed response to guarded pcall."
#define BUGGY_SCRIPT_ERROR "666: Killing badly-behaved Lua script."
#define CL_RESETSTACK_RETURN(ls, oldtop, retval) \
do \
{\
if (oldtop != lua_gettop(ls)) \
{ \
lua_settop(ls, oldtop); \
} \
return (retval); \
} \
while (false)
static int _clua_panic(lua_State *);
static void _clua_throttle_hook(lua_State *, lua_Debug *);
static void *_clua_allocator(void *ud, void *ptr, size_t osize, size_t nsize);
static int _clua_guarded_pcall(lua_State *);
static int _clua_require(lua_State *);
static int _clua_dofile(lua_State *);
static int _clua_loadfile(lua_State *);
CLua::CLua(bool managed)
: error(), managed_vm(managed), shutting_down(false),
throttle_unit_lines(10000),
throttle_sleep_ms(0), throttle_sleep_start(2),
throttle_sleep_end(800), n_throttle_sleeps(0), mixed_call_depth(0),
lua_call_depth(0), max_mixed_call_depth(8),
max_lua_call_depth(100), memory_used(0),
_state(NULL), sourced_files(), uniqindex(0L)
{
}
CLua::~CLua()
{
// Copy the listener vector, because listeners may remove
// themselves from the listener list when we notify them of a
// shutdown.
const std::vector<lua_shutdown_listener*> slisteners = shutdown_listeners;
for (int i = 0, size = slisteners.size(); i < size; ++i)
slisteners[i]->shutdown(*this);
shutting_down = true;
if (_state)
lua_close(_state);
}
lua_State *CLua::state()
{
if (!_state)
init_lua();
return _state;
}
void CLua::setglobal(const char *name)
{
lua_setglobal(state(), name);
}
void CLua::getglobal(const char *name)
{
lua_getglobal(state(), name);
}
std::string CLua::setuniqregistry()
{
char name[100];
snprintf(name, sizeof name, "__cru%lu", uniqindex++);
lua_pushstring(state(), name);
lua_insert(state(), -2);
lua_settable(state(), LUA_REGISTRYINDEX);
return (name);
}
void CLua::setregistry(const char *name)
{
lua_pushstring(state(), name);
// Slide name round before the value
lua_insert(state(), -2);
lua_settable(state(), LUA_REGISTRYINDEX);
}
void CLua::_getregistry(lua_State *ls, const char *name)
{
lua_pushstring(ls, name);
lua_gettable(ls, LUA_REGISTRYINDEX);
}
void CLua::getregistry(const char *name)
{
_getregistry(state(), name);
}
void CLua::save(const char *file)
{
if (!_state)
return;
CLuaSave clsave = { file, NULL };
callfn("c_save", "u", &clsave);
if (clsave.handle)
fclose(clsave.handle);
}
int CLua::file_write(lua_State *ls)
{
if (!lua_islightuserdata(ls, 1))
{
luaL_argerror(ls, 1, "Expected filehandle at arg 1");
return (0);
}
CLuaSave *sf = static_cast<CLuaSave *>( lua_touserdata(ls, 1) );
if (!sf)
return (0);
FILE *f = sf->get_file();
if (!f)
return (0);
const char *text = luaL_checkstring(ls, 2);
if (text)
fprintf(f, "%s", text);
return (0);
}
FILE *CLua::CLuaSave::get_file()
{
if (!handle)
handle = fopen(filename, "w");
return (handle);
}
void CLua::set_error(int err, lua_State *ls)
{
if (!err)
{
error.clear();
return;
}
if (!ls && !(ls = _state))
{
error = "<LUA not initialised>";
return;
}
const char *serr = lua_tostring(ls, -1);
lua_pop(ls, 1);
error = serr? serr : "<Unknown error>";
}
void CLua::init_throttle()
{
if (!managed_vm)
return;
if (throttle_unit_lines <= 0)
throttle_unit_lines = 500;
if (throttle_sleep_start < 1)
throttle_sleep_start = 1;
if (throttle_sleep_end < throttle_sleep_start)
throttle_sleep_end = throttle_sleep_start;
if (!mixed_call_depth)
{
lua_sethook(_state, _clua_throttle_hook,
LUA_MASKCOUNT, throttle_unit_lines);
throttle_sleep_ms = 0;
n_throttle_sleeps = 0;
}
}
int CLua::loadbuffer(const char *buf, size_t size, const char *context)
{
const int err = luaL_loadbuffer(state(), buf, size, context);
set_error(err, state());
return err;
}
int CLua::loadstring(const char *s, const char *context)
{
return loadbuffer(s, strlen(s), context);
}
int CLua::execstring(const char *s, const char *context, int nresults)
{
int err = 0;
if ((err = loadstring(s, context)))
return (err);
lua_State *ls = state();
lua_call_throttle strangler(this);
err = lua_pcall(ls, 0, nresults, 0);
set_error(err, ls);
return err;
}
bool CLua::is_path_safe(std::string s, bool trusted)
{
lowercase(s);
return (s.find("..") == std::string::npos && shell_safe(s.c_str())
&& (trusted || s.find("clua") == std::string::npos));
}
int CLua::loadfile(lua_State *ls, const char *filename, bool trusted,
bool die_on_fail)
{
if (!ls)
return (-1);
if (!is_path_safe(filename, trusted))
{
lua_pushstring(
ls,
make_stringf("invalid filename: %s", filename).c_str());
return (-1);
}
std::string file = datafile_path(filename, die_on_fail);
if (file.empty())
{
lua_pushstring(ls,
make_stringf("Can't find \"%s\"", filename).c_str());
return (-1);
}
return (luaL_loadfile(ls, file.c_str()));
}
int CLua::execfile(const char *filename, bool trusted, bool die_on_fail)
{
if (sourced_files.find(filename) != sourced_files.end())
return 0;
sourced_files.insert(filename);
lua_State *ls = state();
int err = loadfile(ls, filename, trusted || !managed_vm, die_on_fail);
lua_call_throttle strangler(this);
if (!err)
err = lua_pcall(ls, 0, 0, 0);
set_error(err);
if (die_on_fail && !error.empty())
end(1, false, "Lua execfile error (%s): %s",
filename, dlua.error.c_str());
return (err);
}
bool CLua::runhook(const char *hook, const char *params, ...)
{
error.clear();
lua_State *ls = state();
if (!ls)
return (false);
// Remember top of stack, for debugging porpoises
int stack_top = lua_gettop(ls);
pushglobal(hook);
if (!lua_istable(ls, -1))
{
lua_pop(ls, 1);
CL_RESETSTACK_RETURN( ls, stack_top, false );
}
for (int i = 1; ; ++i)
{
int currtop = lua_gettop(ls);
lua_rawgeti(ls, -1, i);
if (!lua_isfunction(ls, -1))
{
lua_pop(ls, 1);
break;
}
// So what's on top *is* a function. Call it with the args we have.
va_list args;
va_start(args, params);
calltopfn(ls, params, args);
va_end(args);
lua_settop(ls, currtop);
}
CL_RESETSTACK_RETURN( ls, stack_top, true );
}
void CLua::fnreturns(const char *format, ...)
{
lua_State *ls = _state;
if (!format || !ls)
return;
va_list args;
va_start(args, format);
vfnreturns(format, args);
va_end(args);
}
void CLua::vfnreturns(const char *format, va_list args)
{
lua_State *ls = _state;
int nrets = return_count(ls, format);
int sp = -nrets - 1;
const char *gs = strchr(format, '>');
if (gs)
format = gs + 1;
else if ((gs = strchr(format, ':')))
format = gs + 1;
for (const char *run = format; *run; ++run)
{
char argtype = *run;
++sp;
switch (argtype)
{
case 'u':
if (lua_islightuserdata(ls, sp))
*(va_arg(args, void**)) = lua_touserdata(ls, sp);
break;
case 'd':
if (lua_isnumber(ls, sp))
*(va_arg(args, int*)) = luaL_checkint(ls, sp);
break;
case 'b':
*(va_arg(args, bool *)) = lua_toboolean(ls, sp);
break;
case 's':
{
const char *s = lua_tostring(ls, sp);
if (s)
*(va_arg(args, std::string *)) = s;
break;
}
default:
break;
}
}
// Pop args off the stack
lua_pop(ls, nrets);
}
int CLua::push_args(lua_State *ls, const char *format, va_list args,
va_list *targ)
{
if (!format)
{
if (targ)
va_copy(*targ, args);
return (0);
}
const char *cs = strchr(format, ':');
if (cs)
format = cs + 1;
int argc = 0;
for (const char *run = format; *run; run++)
{
if (*run == '>')
break;
char argtype = *run;
++argc;
switch (argtype)
{
case 'u': // Light userdata
lua_pushlightuserdata(ls, va_arg(args, void*));
break;
case 's': // String
{
const char *s = va_arg(args, const char *);
if (s)
lua_pushstring(ls, s);
else
lua_pushnil(ls);
break;
}
case 'd': // Integer
lua_pushnumber(ls, va_arg(args, int));
break;
case 'L':
lua_pushnumber(ls, va_arg(args, long));
break;
case 'b':
lua_pushboolean(ls, va_arg(args, int));
break;
case 'D':
clua_push_dgn_event(ls, va_arg(args, const dgn_event *));
break;
case 'm':
clua_push_map(ls, va_arg(args, map_def *));
break;
case 'M':
push_monster(ls, va_arg(args, monsters *));
break;
case 'A':
argc += push_activity_interrupt(
ls, va_arg(args, activity_interrupt_data *));
break;
default:
--argc;
break;
}
}
if (targ)
va_copy(*targ, args);
return (argc);
}
int CLua::return_count(lua_State *ls, const char *format)
{
if (!format)
return (0);
const char *gs = strchr(format, '>');
if (gs)
return (strlen(gs + 1));
const char *cs = strchr(format, ':');
if (cs && isdigit(*format))
{
char *es = NULL;
int ci = strtol(format, &es, 10);
// We're capping return at 10 here, which is arbitrary, but avoids
// blowing the stack.
if (ci < 0)
ci = 0;
else if (ci > 10)
ci = 10;
return (ci);
}
return (0);
}
bool CLua::calltopfn(lua_State *ls, const char *params, va_list args,
int retc, va_list *copyto)
{
// We guarantee to remove the function from the stack
int argc = push_args(ls, params, args, copyto);
if (retc == -1)
retc = return_count(ls, params);
lua_call_throttle strangler(this);
int err = lua_pcall(ls, argc, retc, 0);
set_error(err, ls);
return (!err);
}
maybe_bool CLua::callmbooleanfn(const char *fn, const char *params,
va_list args)
{
error.clear();
lua_State *ls = state();
if (!ls)
return (B_MAYBE);
int stacktop = lua_gettop(ls);
pushglobal(fn);
if (!lua_isfunction(ls, -1))
{
lua_pop(ls, 1);
CL_RESETSTACK_RETURN(ls, stacktop, B_MAYBE);
}
bool ret = calltopfn(ls, params, args, 1);
if (!ret)
CL_RESETSTACK_RETURN(ls, stacktop, B_MAYBE);
maybe_bool r = frombool(lua_toboolean(ls, -1));
CL_RESETSTACK_RETURN(ls, stacktop, r);
}
maybe_bool CLua::callmbooleanfn(const char *fn, const char *params, ...)
{
va_list args;
va_start(args, params);
return (callmbooleanfn(fn, params, args));
}
bool CLua::callbooleanfn(bool def, const char *fn, const char *params, ...)
{
va_list args;
va_start(args, params);
maybe_bool r = callmbooleanfn(fn, params, args);
return (tobool(r, def));
}
bool CLua::proc_returns(const char *par) const
{
return (strchr(par, '>') != NULL);
}
// Identical to lua_getglobal for simple names, but will look up
// "a.b.c" names in tables, so you can pushglobal("dgn.point") and get
// _G['dgn']['point'], as expected.
//
// Guarantees to push exactly one value onto the stack.
//
void CLua::pushglobal(const std::string &name)
{
std::vector<std::string> pieces = split_string(".", name);
lua_State *ls(state());
if (pieces.empty())
lua_pushnil(ls);
for (unsigned i = 0, size = pieces.size(); i < size; ++i)
{
if (!i)
lua_getglobal(ls, pieces[i].c_str());
else
{
if (lua_istable(ls, -1))
{
lua_pushstring(ls, pieces[i].c_str());
lua_gettable(ls, -2);
// Swap the value we just found with the table itself.
lua_insert(ls, -2);
// And remove the table.
lua_pop(ls, 1);
}
else
{
// We expected a table here, but got something else. Fail.
lua_pop(ls, 1);
lua_pushnil(ls);
break;
}
}
}
}
bool CLua::callfn(const char *fn, const char *params, ...)
{
error.clear();
lua_State *ls = state();
if (!ls)
return (false);
pushglobal(fn);
if (!lua_isfunction(ls, -1))
{
lua_pop(ls, 1);
return (false);
}
va_list args;
va_list fnret;
va_start(args, params);
bool ret = calltopfn(ls, params, args, -1, &fnret);
if (ret)
{
// If we have a > in format, gather return params now.
if (proc_returns(params))
vfnreturns(params, fnret);
}
va_end(args);
va_end(fnret);
return (ret);
}
bool CLua::callfn(const char *fn, int nargs, int nret)
{
error.clear();
lua_State *ls = state();
if (!ls)
return (false);
// If a function is not provided on the stack, get the named function.
if (fn)
{
pushglobal(fn);
if (!lua_isfunction(ls, -1))
{
lua_settop(ls, -nargs - 2);
return (false);
}
// Slide the function in front of its args and call it.
if (nargs)
lua_insert(ls, -nargs - 1);
}
lua_call_throttle strangler(this);
int err = lua_pcall(ls, nargs, nret, 0);
set_error(err, ls);
return !err;
}
void CLua::init_lua()
{
if (_state)
return;
_state = managed_vm? lua_newstate(_clua_allocator, this) : luaL_newstate();
if (!_state)
end(1, false, "Unable to create Lua state.");
lua_stack_cleaner clean(_state);
lua_atpanic(_state, _clua_panic);
luaopen_base(_state);
luaopen_string(_state);
luaopen_table(_state);
luaopen_math(_state);
// Open Crawl bindings
cluaopen_kills(_state);
cluaopen_you(_state);
cluaopen_item(_state);
cluaopen_food(_state);
cluaopen_crawl(_state);
cluaopen_file(_state);
cluaopen_moninf(_state);
cluaopen_options(_state);
cluaopen_travel(_state);
cluaopen_view(_state);
cluaopen_globals(_state);
load_cmacro();
load_chooks();
lua_register(_state, "loadfile", _clua_loadfile);
lua_register(_state, "dofile", _clua_dofile);
lua_register(_state, "require", _clua_require);
execfile("clua/util.lua", true, true);
execfile("clua/iter.lua", true, true);
execfile("clua/init.lua", true, true);
if (managed_vm)
{
lua_register(_state, "pcall", _clua_guarded_pcall);
execfile("clua/userbase.lua", true, true);
}
lua_pushboolean(_state, managed_vm);
setregistry("lua_vm_is_managed");
lua_pushlightuserdata(_state, this);
setregistry("__clua");
}
CLua &CLua::get_vm(lua_State *ls)
{
lua_stack_cleaner clean(ls);
_getregistry(ls, "__clua");
CLua *vm = util_get_userdata<CLua>(ls, -1);
if (!vm)
end(1, false, "Failed to find CLua for lua state %p", ls);
return (*vm);
}
bool CLua::is_managed_vm(lua_State *ls)
{
lua_stack_cleaner clean(ls);
lua_pushstring(ls, "lua_vm_is_managed");
lua_gettable(ls, LUA_REGISTRYINDEX);
return (lua_toboolean(ls, -1));
}
void CLua::load_chooks()
{
// All hook names must be chk_????
static const char *c_hooks =
"chk_startgame = { }"
;
execstring(c_hooks, "base");
}
void CLua::load_cmacro()
{
execfile("clua/macro.lua", true, true);
}
void CLua::add_shutdown_listener(lua_shutdown_listener *listener)
{
if (std::find(shutdown_listeners.begin(), shutdown_listeners.end(),
listener) == shutdown_listeners.end())
shutdown_listeners.push_back(listener);
}
void CLua::remove_shutdown_listener(lua_shutdown_listener *listener)
{
std::vector<lua_shutdown_listener*>::iterator i =
std::find(shutdown_listeners.begin(), shutdown_listeners.end(),
listener);
if (i != shutdown_listeners.end())
shutdown_listeners.erase(i);
}
// Can be called from within a debugger to look at the current Lua
// call stack. (Borrowed from ToME 3)
void CLua::print_stack()
{
struct lua_Debug dbg;
int i = 0;
lua_State *L = state();
fprintf(stderr, EOL);
while (lua_getstack(L, i++, &dbg) == 1)
{
lua_getinfo(L, "lnuS", &dbg);
char* file = strrchr(dbg.short_src, '/');
if (file == NULL)
file = dbg.short_src;
else
file++;
fprintf(stderr, "%s, function %s, line %d" EOL, file,
dbg.name, dbg.currentline);
}
fprintf(stderr, EOL);
}
////////////////////////////////////////////////////////////////////////
// lua_text_pattern
// We could simplify this a great deal by just using lex and yacc, but I
// don't know if we want to introduce them.
struct lua_pat_op
{
const char *token;
const char *luatok;
bool pretext; // Does this follow a pattern?
bool posttext; // Is this followed by a pattern?
};
static lua_pat_op pat_ops[] =
{
{ "<<", " ( ", false, true },
{ ">>", " ) ", true, false },
{ "!!", " not ", false, true },
{ "==", " == ", true, true },
{ "^^", " ~= ", true, true },
{ "&&", " and ", true, true },
{ "||", " or ", true, true },
};
unsigned long lua_text_pattern::lfndx = 0L;
bool lua_text_pattern::is_lua_pattern(const std::string &s)
{
for (int i = 0, size = sizeof(pat_ops) / sizeof(*pat_ops);
i < size; ++i)
{
if (s.find(pat_ops[i].token) != std::string::npos)
return (true);
}
return (false);
}
lua_text_pattern::lua_text_pattern(const std::string &_pattern)
: translated(false), isvalid(true), pattern(_pattern), lua_fn_name()
{
lua_fn_name = new_fn_name();
}
lua_text_pattern::~lua_text_pattern()
{
if (translated && !lua_fn_name.empty())
{
lua_State *ls = clua;
if (ls)
{
lua_pushnil(ls);
clua.setglobal(lua_fn_name.c_str());
}
}
}
bool lua_text_pattern::valid() const
{
return translated? isvalid : translate();
}
bool lua_text_pattern::matches( const std::string &s ) const
{
if (isvalid && !translated)
translate();
if (!isvalid)
return (false);
return clua.callbooleanfn(false, lua_fn_name.c_str(), "s", s.c_str());
}
void lua_text_pattern::pre_pattern(std::string &pat, std::string &fn) const
{
// Trim trailing spaces
pat.erase( pat.find_last_not_of(" \t\n\r") + 1 );
fn += " pmatch([[";
fn += pat;
fn += "]], text, false) ";
pat.clear();
}
void lua_text_pattern::post_pattern(std::string &pat, std::string &fn) const
{
pat.erase( 0, pat.find_first_not_of(" \t\n\r") );
fn += " pmatch([[";
fn += pat;
fn += "]], text, false) ";
pat.clear();
}
std::string lua_text_pattern::new_fn_name()
{
return (make_stringf("__ch_stash_search_%lu", lfndx++));
}
bool lua_text_pattern::translate() const
{
if (translated || !isvalid)
return (false);
if (pattern.find("]]") != std::string::npos
|| pattern.find("[[") != std::string::npos)
{
return (false);
}
std::string textp;
std::string luafn;
const lua_pat_op *currop = NULL;
for (std::string::size_type i = 0; i < pattern.length(); ++i)
{
bool match = false;
for (unsigned p = 0; p < sizeof pat_ops / sizeof *pat_ops; ++p)
{
const lua_pat_op &lop = pat_ops[p];
if (pattern.find(lop.token, i) == i)
{
match = true;
if (lop.pretext && (!currop || currop->posttext))
{
if (currop)
textp.erase(0, textp.find_first_not_of(" \r\n\t"));
pre_pattern(textp, luafn);
}
currop = &lop;
luafn += lop.luatok;
i += strlen( lop.token ) - 1;
break;
}
}
if (match)
continue;
textp += pattern[i];
}
if (currop && currop->posttext)
post_pattern(textp, luafn);
luafn = "function " + lua_fn_name + "(text) return " + luafn + " end";
const_cast<lua_text_pattern *>(this)->translated = true;
int err = clua.execstring( luafn.c_str(), "stash-search" );
if (err)
{
lua_text_pattern *self = const_cast<lua_text_pattern *>(this);
self->isvalid = self->translated = false;
}
return translated;
}
//////////////////////////////////////////////////////////////////////////
lua_call_throttle::lua_clua_map lua_call_throttle::lua_map;
// A panic function for the Lua interpreter, usually called when it
// runs out of memory when trying to load a file or a chunk of Lua from
// an unprotected Lua op. The only cases of unprotected Lua loads are
// loads of Lua code from .crawlrc, which is read at start of game.
//
// If there's an inordinately large .crawlrc (we're talking seriously
// massive here) that wants more memory than we're willing to give
// Lua, then the game will save and exit until the .crawlrc is fixed.
//
// Lua can also run out of memory during protected script execution,
// such as when running a macro or some other game hook, but in such
// cases the Lua interpreter will throw an exception instead of
// panicking.
//
static int _clua_panic(lua_State *ls)
{
if (crawl_state.need_save && !crawl_state.saving_game
&& !crawl_state.updating_scores)
{
save_game(true);
}
return (0);
}
static void *_clua_allocator(void *ud, void *ptr, size_t osize, size_t nsize)
{
CLua *cl = static_cast<CLua *>( ud );
cl->memory_used += nsize - osize;
if (nsize > osize && cl->memory_used >= CLUA_MAX_MEMORY_USE * 1024
&& cl->mixed_call_depth)
{
return (NULL);
}
if (!nsize)
{
free(ptr);
return (NULL);
}
else
return (realloc(ptr, nsize));
}
static void _clua_throttle_hook(lua_State *ls, lua_Debug *dbg)
{
CLua *lua = lua_call_throttle::find_clua(ls);
// Co-routines can create a new Lua state; in such cases, we must
// fudge it.
if (!lua)
lua = &clua;
if (lua)
{
if (!lua->throttle_sleep_ms)
lua->throttle_sleep_ms = lua->throttle_sleep_start;
else if (lua->throttle_sleep_ms < lua->throttle_sleep_end)
lua->throttle_sleep_ms *= 2;
++lua->n_throttle_sleeps;
delay(lua->throttle_sleep_ms);
// Try to kill the annoying script.
if (lua->n_throttle_sleeps > CLua::MAX_THROTTLE_SLEEPS)
{
lua->n_throttle_sleeps = CLua::MAX_THROTTLE_SLEEPS;
luaL_error(ls, BUGGY_SCRIPT_ERROR);
}
}
}
lua_call_throttle::lua_call_throttle(CLua *_lua)
: lua(_lua)
{
lua->init_throttle();
if (!lua->mixed_call_depth++)
lua_map[lua->state()] = lua;
}
lua_call_throttle::~lua_call_throttle()
{
if (!--lua->mixed_call_depth)
lua_map.erase(lua->state());
}
CLua *lua_call_throttle::find_clua(lua_State *ls)
{
lua_clua_map::iterator i = lua_map.find(ls);
return (i != lua_map.end()? i->second : NULL);
}
// This function is a replacement for Lua's in-built pcall function. It behaves
// like pcall in all respects (as documented in the Lua 5.1 reference manual),
// but does not allow the Lua chunk/script to catch errors thrown by the
// Lua-throttling code. This is necessary so that we can interrupt scripts that
// are hogging CPU.
//
// If we did not intercept pcall, the script could do the equivalent
// of this:
//
// while true do
// pcall(function () while true do end end)
// end
//
// And there's a good chance we wouldn't be able to interrupt the
// deadloop because our errors would get caught by the pcall (more
// levels of nesting would just increase the chance of the script
// beating our throttling).
//
static int _clua_guarded_pcall(lua_State *ls)
{
const int nargs = lua_gettop(ls);
const int err = lua_pcall(ls, nargs - 1, LUA_MULTRET, 0);
if (err)
{
const char *errs = lua_tostring(ls, 1);
if (!errs || strstr(errs, BUGGY_SCRIPT_ERROR))
luaL_error(ls, errs? errs : BUGGY_PCALL_ERROR);
}
lua_pushboolean(ls, !err);
lua_insert(ls, 1);
return (lua_gettop(ls));
}
static int _clua_loadfile(lua_State *ls)
{
const char *file = luaL_checkstring(ls, 1);
if (!file)
return (0);
const int err = CLua::loadfile(ls, file, !CLua::is_managed_vm(ls));
if (err)
{
const int place = lua_gettop(ls);
lua_pushnil(ls);
lua_insert(ls, place);
return (2);
}
return (1);
}
static int _clua_require(lua_State *ls)
{
const char *file = luaL_checkstring(ls, 1);
if (!file)
return (0);
CLua &vm(CLua::get_vm(ls));
if (vm.execfile(file, false, false) != 0)
luaL_error(ls, vm.error.c_str());
lua_pushboolean(ls, true);
return (1);
}
static int _clua_dofile(lua_State *ls)
{
const char *file = luaL_checkstring(ls, 1);
if (!file)
return (0);
const int err = CLua::loadfile(ls, file, !CLua::is_managed_vm(ls));
if (err)
return (lua_error(ls));
lua_call(ls, 0, LUA_MULTRET);
return (lua_gettop(ls));
}
std::string quote_lua_string(const std::string &s)
{
return replace_all_of(replace_all_of(s, "\\", "\\\\"), "\"", "\\\"");
}
/////////////////////////////////////////////////////////////////////
lua_shutdown_listener::~lua_shutdown_listener()
{
}
lua_datum::lua_datum(CLua &_lua, int stackpos, bool pop)
: lua(_lua), need_cleanup(true)
{
// Store the datum in the registry indexed by "this".
lua_pushvalue(lua, stackpos);
lua_pushlightuserdata(lua, this);
// Move the key (this) before the value.
lua_insert(lua, -2);
lua_settable(lua, LUA_REGISTRYINDEX);
if (pop && stackpos < 0)
lua_pop(lua, -stackpos);
lua.add_shutdown_listener(this);
}
lua_datum::lua_datum(const lua_datum &o)
: lua(o.lua), need_cleanup(true)
{
set_from(o);
}
void lua_datum::set_from(const lua_datum &o)
{
lua_pushlightuserdata(lua, this);
o.push();
lua_settable(lua, LUA_REGISTRYINDEX);
lua.add_shutdown_listener(this);
need_cleanup = true;
}
const lua_datum &lua_datum::operator = (const lua_datum &o)
{
if (this != &o)
{
cleanup();
set_from(o);
}
return (*this);
}
void lua_datum::push() const
{
lua_pushlightuserdata(lua, const_cast<lua_datum*>(this));
lua_gettable(lua, LUA_REGISTRYINDEX);
// The value we saved is now on top of the Lua stack.
}
lua_datum::~lua_datum()
{
cleanup();
}
void lua_datum::shutdown(CLua &)
{
cleanup();
}
void lua_datum::cleanup()
{
if (need_cleanup)
{
need_cleanup = false;
lua.remove_shutdown_listener(this);
lua_pushlightuserdata(lua, this);
lua_pushnil(lua);
lua_settable(lua, LUA_REGISTRYINDEX);
}
}
#define LUA_CHECK_TYPE(check) \
lua_stack_cleaner clean(lua); \
push(); \
return check(lua, -1)
bool lua_datum::is_table() const
{
LUA_CHECK_TYPE(lua_istable);
}
bool lua_datum::is_function() const
{
LUA_CHECK_TYPE(lua_isfunction);
}
bool lua_datum::is_number() const
{
LUA_CHECK_TYPE(lua_isnumber);
}
bool lua_datum::is_string() const
{
LUA_CHECK_TYPE(lua_isstring);
}
bool lua_datum::is_udata() const
{
LUA_CHECK_TYPE(lua_isuserdata);
}