2012-09-29 20:03:37 -06:00
|
|
|
/*
|
2012-03-31 05:40:54 -06:00
|
|
|
https://github.com/peterix/dfhack
|
2012-09-29 20:03:37 -06:00
|
|
|
Copyright (c) 2009-2012 Petr Mrázek (peterix@gmail.com)
|
2012-03-31 05:40:54 -06:00
|
|
|
|
|
|
|
This software is provided 'as-is', without any express or implied
|
|
|
|
warranty. In no event will the authors be held liable for any
|
|
|
|
damages arising from the use of this software.
|
|
|
|
|
|
|
|
Permission is granted to anyone to use this software for any
|
|
|
|
purpose, including commercial applications, and to alter it and
|
|
|
|
redistribute it freely, subject to the following restrictions:
|
|
|
|
|
|
|
|
1. The origin of this software must not be misrepresented; you must
|
|
|
|
not claim that you wrote the original software. If you use this
|
|
|
|
software in a product, an acknowledgment in the product documentation
|
|
|
|
would be appreciated but is not required.
|
|
|
|
|
|
|
|
2. Altered source versions must be plainly marked as such, and
|
|
|
|
must not be misrepresented as being the original software.
|
|
|
|
|
|
|
|
3. This notice may not be removed or altered from any source
|
|
|
|
distribution.
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include "Internal.h"
|
|
|
|
|
2015-05-24 17:06:01 -06:00
|
|
|
#include <csignal>
|
2012-03-31 05:40:54 -06:00
|
|
|
#include <string>
|
|
|
|
#include <vector>
|
|
|
|
#include <map>
|
|
|
|
|
|
|
|
#include "MemAccess.h"
|
|
|
|
#include "Core.h"
|
|
|
|
#include "VersionInfo.h"
|
|
|
|
#include "tinythread.h"
|
|
|
|
// must be last due to MS stupidity
|
|
|
|
#include "DataDefs.h"
|
|
|
|
#include "DataIdentity.h"
|
2012-04-05 01:59:39 -06:00
|
|
|
#include "DataFuncs.h"
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-01 06:43:40 -06:00
|
|
|
#include "modules/World.h"
|
2012-04-05 01:59:39 -06:00
|
|
|
#include "modules/Gui.h"
|
2012-04-05 09:55:59 -06:00
|
|
|
#include "modules/Job.h"
|
2022-11-04 18:54:18 -06:00
|
|
|
#include "modules/Screen.h"
|
2012-04-06 01:21:28 -06:00
|
|
|
#include "modules/Translation.h"
|
|
|
|
#include "modules/Units.h"
|
2012-04-01 06:43:40 -06:00
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
#include "LuaWrapper.h"
|
|
|
|
#include "LuaTools.h"
|
|
|
|
|
|
|
|
#include "MiscUtils.h"
|
2015-02-19 08:31:58 -07:00
|
|
|
#include "DFHackVersion.h"
|
2015-08-11 20:59:19 -06:00
|
|
|
#include "PluginManager.h"
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2022-11-04 18:54:18 -06:00
|
|
|
#include "df/building.h"
|
|
|
|
#include "df/enabler.h"
|
|
|
|
#include "df/entity_position.h"
|
|
|
|
#include "df/entity_position_assignment.h"
|
|
|
|
#include "df/historical_entity.h"
|
|
|
|
#include "df/item.h"
|
2012-04-05 01:59:39 -06:00
|
|
|
#include "df/job.h"
|
2012-04-05 09:55:59 -06:00
|
|
|
#include "df/job_item.h"
|
|
|
|
#include "df/unit.h"
|
2012-05-04 10:59:06 -06:00
|
|
|
#include "df/world.h"
|
2012-04-05 01:59:39 -06:00
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
#include <lua.h>
|
|
|
|
#include <lauxlib.h>
|
|
|
|
#include <lualib.h>
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
#include <lstate.h>
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
using namespace DFHack;
|
|
|
|
using namespace DFHack::LuaWrapper;
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
lua_State *DFHack::Lua::Core::State = NULL;
|
|
|
|
|
2012-06-13 11:12:36 -06:00
|
|
|
void dfhack_printerr(lua_State *S, const std::string &str);
|
|
|
|
|
2012-08-23 09:27:12 -06:00
|
|
|
inline bool is_null_userdata(lua_State *L, int idx)
|
|
|
|
{
|
|
|
|
return lua_islightuserdata(L, idx) && !lua_touserdata(L, idx);
|
|
|
|
}
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
inline void AssertCoreSuspend(lua_State *state)
|
|
|
|
{
|
|
|
|
assert(!Lua::IsCoreContext(state) || DFHack::Core::getInstance().isSuspended());
|
|
|
|
}
|
|
|
|
|
2022-11-04 18:54:18 -06:00
|
|
|
/*
|
|
|
|
* Lua Push methods
|
|
|
|
*/
|
|
|
|
|
|
|
|
void DFHack::Lua::Push(lua_State *state, const Units::NoblePosition &pos)
|
|
|
|
{
|
|
|
|
lua_createtable(state, 0, 3);
|
|
|
|
Lua::PushDFObject(state, pos.entity);
|
|
|
|
lua_setfield(state, -2, "entity");
|
|
|
|
Lua::PushDFObject(state, pos.assignment);
|
|
|
|
lua_setfield(state, -2, "assignment");
|
|
|
|
Lua::PushDFObject(state, pos.position);
|
|
|
|
lua_setfield(state, -2, "position");
|
|
|
|
}
|
|
|
|
|
|
|
|
void DFHack::Lua::Push(lua_State *state, df::coord pos)
|
|
|
|
{
|
|
|
|
lua_createtable(state, 0, 3);
|
|
|
|
lua_pushinteger(state, pos.x);
|
|
|
|
lua_setfield(state, -2, "x");
|
|
|
|
lua_pushinteger(state, pos.y);
|
|
|
|
lua_setfield(state, -2, "y");
|
|
|
|
lua_pushinteger(state, pos.z);
|
|
|
|
lua_setfield(state, -2, "z");
|
|
|
|
}
|
|
|
|
|
|
|
|
void DFHack::Lua::Push(lua_State *state, df::coord2d pos)
|
|
|
|
{
|
|
|
|
lua_createtable(state, 0, 2);
|
|
|
|
lua_pushinteger(state, pos.x);
|
|
|
|
lua_setfield(state, -2, "x");
|
|
|
|
lua_pushinteger(state, pos.y);
|
|
|
|
lua_setfield(state, -2, "y");
|
|
|
|
}
|
|
|
|
|
|
|
|
void DFHack::Lua::GetVector(lua_State *state, std::vector<std::string> &pvec)
|
|
|
|
{
|
|
|
|
lua_pushnil(state); // first key
|
|
|
|
while (lua_next(state, 1) != 0)
|
|
|
|
{
|
|
|
|
pvec.push_back(lua_tostring(state, -1));
|
|
|
|
lua_pop(state, 1); // remove value, leave key
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void DFHack::Lua::PushInterfaceKeys(lua_State *L,
|
|
|
|
const std::set<df::interface_key> &keys) {
|
|
|
|
lua_createtable(L, 0, keys.size() + 5);
|
|
|
|
|
|
|
|
for (auto &key : keys)
|
|
|
|
{
|
|
|
|
if (auto name = enum_item_raw_key(key))
|
|
|
|
lua_pushstring(L, name);
|
|
|
|
else
|
|
|
|
lua_pushinteger(L, key);
|
|
|
|
|
|
|
|
lua_pushboolean(L, true);
|
|
|
|
lua_rawset(L, -3);
|
|
|
|
|
|
|
|
int charval = Screen::keyToChar(key);
|
|
|
|
if (charval >= 0)
|
|
|
|
{
|
|
|
|
lua_pushinteger(L, charval);
|
|
|
|
lua_setfield(L, -2, "_STRING");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (df::global::enabler) {
|
|
|
|
if (df::global::enabler->mouse_lbut_down) {
|
|
|
|
lua_pushboolean(L, true);
|
|
|
|
lua_setfield(L, -2, "_MOUSE_L");
|
|
|
|
}
|
|
|
|
if (df::global::enabler->mouse_rbut_down) {
|
|
|
|
lua_pushboolean(L, true);
|
|
|
|
lua_setfield(L, -2, "_MOUSE_R");
|
|
|
|
}
|
|
|
|
if (df::global::enabler->mouse_lbut) {
|
|
|
|
lua_pushboolean(L, true);
|
|
|
|
lua_setfield(L, -2, "_MOUSE_L_DOWN");
|
|
|
|
df::global::enabler->mouse_lbut = 0;
|
|
|
|
}
|
|
|
|
if (df::global::enabler->mouse_rbut) {
|
|
|
|
lua_pushboolean(L, true);
|
|
|
|
lua_setfield(L, -2, "_MOUSE_R_DOWN");
|
|
|
|
df::global::enabler->mouse_rbut = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int DFHack::Lua::PushPosXYZ(lua_State *state, df::coord pos)
|
|
|
|
{
|
|
|
|
if (!pos.isValid())
|
|
|
|
{
|
|
|
|
lua_pushnil(state);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lua_pushinteger(state, pos.x);
|
|
|
|
lua_pushinteger(state, pos.y);
|
|
|
|
lua_pushinteger(state, pos.z);
|
|
|
|
return 3;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int DFHack::Lua::PushPosXY(lua_State *state, df::coord2d pos)
|
|
|
|
{
|
|
|
|
if (!pos.isValid())
|
|
|
|
{
|
|
|
|
lua_pushnil(state);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lua_pushinteger(state, pos.x);
|
|
|
|
lua_pushinteger(state, pos.y);
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
/*
|
|
|
|
* Public DF object reference handling API
|
|
|
|
*/
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
void DFHack::Lua::PushDFObject(lua_State *state, type_identity *type, void *ptr)
|
|
|
|
{
|
|
|
|
push_object_internal(state, type, ptr, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
void *DFHack::Lua::GetDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type)
|
|
|
|
{
|
|
|
|
return get_object_internal(state, type, val_index, exact_type, false);
|
|
|
|
}
|
|
|
|
|
2012-05-02 02:50:05 -06:00
|
|
|
static void check_valid_ptr_index(lua_State *state, int val_index)
|
2012-04-15 09:09:25 -06:00
|
|
|
{
|
|
|
|
if (lua_type(state, val_index) == LUA_TNONE)
|
|
|
|
{
|
|
|
|
if (val_index > 0)
|
|
|
|
luaL_argerror(state, val_index, "pointer expected");
|
|
|
|
else
|
|
|
|
luaL_error(state, "at index %d: pointer expected", val_index);
|
|
|
|
}
|
2012-05-02 02:50:05 -06:00
|
|
|
}
|
2012-04-15 09:09:25 -06:00
|
|
|
|
2012-05-02 02:50:05 -06:00
|
|
|
static void signal_typeid_error(color_ostream *out, lua_State *state,
|
|
|
|
type_identity *type, const char *msg,
|
|
|
|
int val_index, bool perr, bool signal)
|
|
|
|
{
|
2012-10-28 01:50:28 -06:00
|
|
|
std::string typestr = type ? type->getFullName() : "any pointer";
|
|
|
|
std::string error = stl_sprintf(msg, typestr.c_str());
|
2012-04-15 09:09:25 -06:00
|
|
|
|
2012-05-02 02:50:05 -06:00
|
|
|
if (signal)
|
2012-04-15 09:09:25 -06:00
|
|
|
{
|
|
|
|
if (val_index > 0)
|
|
|
|
luaL_argerror(state, val_index, error.c_str());
|
|
|
|
else
|
|
|
|
luaL_error(state, "at index %d: %s", val_index, error.c_str());
|
|
|
|
}
|
2012-05-02 02:50:05 -06:00
|
|
|
else if (perr)
|
|
|
|
{
|
|
|
|
if (out)
|
|
|
|
out->printerr("%s", error.c_str());
|
|
|
|
else
|
|
|
|
dfhack_printerr(state, error);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
lua_pushstring(state, error.c_str());
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
void *DFHack::Lua::CheckDFObject(lua_State *state, type_identity *type, int val_index, bool exact_type)
|
|
|
|
{
|
|
|
|
check_valid_ptr_index(state, val_index);
|
|
|
|
|
|
|
|
if (lua_isnil(state, val_index))
|
|
|
|
return NULL;
|
2012-10-28 01:50:28 -06:00
|
|
|
if (lua_islightuserdata(state, val_index) && !lua_touserdata(state, val_index))
|
|
|
|
return NULL;
|
2012-05-02 02:50:05 -06:00
|
|
|
|
|
|
|
void *rv = get_object_internal(state, type, val_index, exact_type, false);
|
|
|
|
|
|
|
|
if (!rv)
|
|
|
|
signal_typeid_error(NULL, state, type, "invalid pointer type; expected: %s",
|
|
|
|
val_index, false, true);
|
2012-04-15 09:09:25 -06:00
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
/*
|
|
|
|
* Console I/O wrappers
|
|
|
|
*/
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
static int DFHACK_OSTREAM_TOKEN = 0;
|
|
|
|
|
|
|
|
color_ostream *DFHack::Lua::GetOutput(lua_State *L)
|
|
|
|
{
|
|
|
|
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_OSTREAM_TOKEN);
|
|
|
|
auto rv = (color_ostream*)lua_touserdata(L, -1);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
2012-04-05 09:55:59 -06:00
|
|
|
df::cur_lua_ostream_argument::cur_lua_ostream_argument(lua_State *state)
|
|
|
|
{
|
|
|
|
out = DFHack::Lua::GetOutput(state);
|
|
|
|
if (!out)
|
|
|
|
LuaWrapper::field_error(state, UPVAL_METHOD_NAME, "no output stream", "invoke");
|
|
|
|
}
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
static void set_dfhack_output(lua_State *L, color_ostream *p)
|
|
|
|
{
|
|
|
|
lua_pushlightuserdata(L, p);
|
|
|
|
lua_rawsetp(L, LUA_REGISTRYINDEX, &DFHACK_OSTREAM_TOKEN);
|
|
|
|
}
|
|
|
|
|
2012-04-04 00:40:33 -06:00
|
|
|
static Console *get_console(lua_State *state)
|
|
|
|
{
|
|
|
|
color_ostream *pstream = Lua::GetOutput(state);
|
|
|
|
|
|
|
|
if (!pstream)
|
|
|
|
{
|
|
|
|
lua_pushnil(state);
|
|
|
|
lua_pushstring(state, "no output stream");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!pstream->is_console())
|
|
|
|
{
|
|
|
|
lua_pushnil(state);
|
|
|
|
lua_pushstring(state, "not an interactive console");
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
return static_cast<Console*>(pstream);
|
|
|
|
}
|
|
|
|
|
2012-04-21 10:15:57 -06:00
|
|
|
static int DFHACK_TOSTRING_TOKEN = 0;
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
static std::string lua_print_fmt(lua_State *L)
|
|
|
|
{
|
|
|
|
/* Copied from lua source to fully replicate builtin print */
|
|
|
|
int n = lua_gettop(L); /* number of arguments */
|
2012-04-21 10:15:57 -06:00
|
|
|
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TOSTRING_TOKEN);
|
2012-03-31 05:40:54 -06:00
|
|
|
|
|
|
|
std::stringstream ss;
|
|
|
|
|
|
|
|
for (int i=1; i<=n; i++) {
|
|
|
|
lua_pushvalue(L, -1); /* function to be called */
|
|
|
|
lua_pushvalue(L, i); /* value to print */
|
|
|
|
lua_call(L, 1, 1);
|
|
|
|
const char *s = lua_tostring(L, -1); /* get result */
|
|
|
|
if (s == NULL)
|
|
|
|
luaL_error(L, "tostring must return a string to print");
|
|
|
|
if (i>1)
|
|
|
|
ss << '\t';
|
|
|
|
ss << s;
|
|
|
|
lua_pop(L, 1); /* pop result */
|
|
|
|
}
|
|
|
|
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
static int lua_dfhack_print(lua_State *S)
|
|
|
|
{
|
|
|
|
std::string str = lua_print_fmt(S);
|
|
|
|
if (color_ostream *out = Lua::GetOutput(S))
|
2014-08-15 03:54:54 -06:00
|
|
|
out->print("%s", str.c_str());//*out << str;
|
2012-03-31 05:40:54 -06:00
|
|
|
else
|
|
|
|
Core::print("%s", str.c_str());
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int lua_dfhack_println(lua_State *S)
|
|
|
|
{
|
|
|
|
std::string str = lua_print_fmt(S);
|
|
|
|
if (color_ostream *out = Lua::GetOutput(S))
|
|
|
|
*out << str << std::endl;
|
|
|
|
else
|
|
|
|
Core::print("%s\n", str.c_str());
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-06-13 11:12:36 -06:00
|
|
|
void dfhack_printerr(lua_State *S, const std::string &str)
|
2012-03-31 05:40:54 -06:00
|
|
|
{
|
|
|
|
if (color_ostream *out = Lua::GetOutput(S))
|
|
|
|
out->printerr("%s\n", str.c_str());
|
|
|
|
else
|
|
|
|
Core::printerr("%s\n", str.c_str());
|
2012-04-04 03:34:07 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static int lua_dfhack_printerr(lua_State *S)
|
|
|
|
{
|
|
|
|
std::string str = lua_print_fmt(S);
|
|
|
|
dfhack_printerr(S, str);
|
2012-03-31 05:40:54 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-04-04 00:40:33 -06:00
|
|
|
static int lua_dfhack_color(lua_State *S)
|
|
|
|
{
|
|
|
|
int cv = luaL_optint(S, 1, -1);
|
|
|
|
|
2012-08-18 23:21:25 -06:00
|
|
|
if (cv < -1 || cv > COLOR_MAX)
|
2012-04-04 00:40:33 -06:00
|
|
|
luaL_argerror(S, 1, "invalid color value");
|
|
|
|
|
|
|
|
color_ostream *out = Lua::GetOutput(S);
|
2012-06-21 11:26:25 -06:00
|
|
|
if (out) {
|
|
|
|
lua_pushinteger(S, (int)out->color());
|
2012-04-04 00:40:33 -06:00
|
|
|
out->color(color_ostream::color_value(cv));
|
2012-06-21 11:26:25 -06:00
|
|
|
return 1;
|
|
|
|
}
|
2012-04-04 00:40:33 -06:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int lua_dfhack_is_interactive(lua_State *S)
|
|
|
|
{
|
|
|
|
lua_pushboolean(S, get_console(S) != NULL);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-04-16 08:05:42 -06:00
|
|
|
static int dfhack_lineedit_sync(lua_State *S, Console *pstream)
|
2012-04-04 00:40:33 -06:00
|
|
|
{
|
2012-04-16 08:05:42 -06:00
|
|
|
const char *prompt = luaL_optstring(S, 1, ">> ");
|
|
|
|
const char *hfile = luaL_optstring(S, 2, NULL);
|
|
|
|
|
2012-04-04 00:40:33 -06:00
|
|
|
DFHack::CommandHistory hist;
|
|
|
|
if (hfile)
|
|
|
|
hist.load(hfile);
|
|
|
|
|
|
|
|
std::string ret;
|
|
|
|
int rv = pstream->lineedit(prompt, ret, hist);
|
|
|
|
|
2018-07-04 06:21:25 -06:00
|
|
|
if (rv == Console::RETRY)
|
|
|
|
rv = 0; /* return empty string to lua */
|
|
|
|
|
2012-04-04 00:40:33 -06:00
|
|
|
if (rv < 0)
|
|
|
|
{
|
|
|
|
lua_pushnil(S);
|
2018-07-04 06:21:25 -06:00
|
|
|
if (rv == Console::SHUTDOWN)
|
2018-06-21 07:57:36 -06:00
|
|
|
lua_pushstring(S, "shutdown requested");
|
|
|
|
else
|
|
|
|
lua_pushstring(S, "input error");
|
2012-04-04 00:40:33 -06:00
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (hfile)
|
|
|
|
hist.save(hfile);
|
|
|
|
lua_pushlstring(S, ret.data(), ret.size());
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-04-16 08:05:42 -06:00
|
|
|
static int DFHACK_QUERY_COROTABLE_TOKEN = 0;
|
|
|
|
|
|
|
|
static int yield_helper(lua_State *S)
|
|
|
|
{
|
|
|
|
return lua_yield(S, lua_gettop(S));
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
2016-07-26 21:47:53 -06:00
|
|
|
int dfhack_lineedit_cont(lua_State *L, int status, lua_KContext)
|
2012-04-16 08:05:42 -06:00
|
|
|
{
|
|
|
|
if (Lua::IsSuccess(status))
|
|
|
|
return lua_gettop(L) - 2;
|
|
|
|
else
|
|
|
|
return dfhack_lineedit_sync(L, get_console(L));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_lineedit(lua_State *S)
|
|
|
|
{
|
|
|
|
lua_settop(S, 2);
|
|
|
|
|
|
|
|
Console *pstream = get_console(S);
|
2018-06-21 07:57:36 -06:00
|
|
|
if (!pstream) {
|
|
|
|
lua_pushnil(S);
|
|
|
|
lua_pushstring(S, "no console");
|
2012-04-16 08:05:42 -06:00
|
|
|
return 2;
|
2018-06-21 07:57:36 -06:00
|
|
|
}
|
2012-04-16 08:05:42 -06:00
|
|
|
|
|
|
|
lua_rawgetp(S, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN);
|
|
|
|
lua_rawgetp(S, -1, S);
|
|
|
|
bool in_coroutine = !lua_isnil(S, -1);
|
|
|
|
lua_settop(S, 2);
|
|
|
|
|
|
|
|
if (in_coroutine)
|
|
|
|
{
|
|
|
|
lua_pushcfunction(S, yield_helper);
|
|
|
|
lua_pushvalue(S, 1);
|
|
|
|
lua_pushvalue(S, 2);
|
|
|
|
return Lua::TailPCallK<dfhack_lineedit_cont>(S, 2, LUA_MULTRET, 0, 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
return dfhack_lineedit_sync(S, pstream);
|
|
|
|
}
|
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
/*
|
|
|
|
* Exception handling
|
|
|
|
*/
|
|
|
|
|
2015-05-24 17:06:01 -06:00
|
|
|
volatile std::sig_atomic_t lstop = 0;
|
|
|
|
|
|
|
|
static void interrupt_hook (lua_State *L, lua_Debug *ar);
|
|
|
|
static void interrupt_init (lua_State *L)
|
|
|
|
{
|
|
|
|
lua_sethook(L, interrupt_hook, LUA_MASKCOUNT, 256);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void interrupt_hook (lua_State *L, lua_Debug *ar)
|
|
|
|
{
|
|
|
|
if (lstop)
|
|
|
|
{
|
|
|
|
lstop = 0;
|
|
|
|
interrupt_init(L); // Restore default settings if necessary
|
|
|
|
luaL_error(L, "interrupted!");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DFHack::Lua::Interrupt (bool force)
|
|
|
|
{
|
|
|
|
lua_State *L = Lua::Core::State;
|
|
|
|
if (L->hook != interrupt_hook && !force)
|
|
|
|
return false;
|
|
|
|
if (force)
|
|
|
|
lua_sethook(L, interrupt_hook, LUA_MASKCALL | LUA_MASKRET | LUA_MASKLINE | LUA_MASKCOUNT, 1);
|
|
|
|
lstop = 1;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
static int DFHACK_EXCEPTION_META_TOKEN = 0;
|
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
static void error_tostring(lua_State *L, bool keep_old = false)
|
2012-04-04 03:34:07 -06:00
|
|
|
{
|
2012-04-21 10:15:57 -06:00
|
|
|
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TOSTRING_TOKEN);
|
2012-04-07 04:21:38 -06:00
|
|
|
if (keep_old)
|
|
|
|
lua_pushvalue(L, -2);
|
|
|
|
else
|
|
|
|
lua_swap(L);
|
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
bool ok = lua_pcall(L, 1, 1, 0) == LUA_OK;
|
|
|
|
|
|
|
|
const char *msg = lua_tostring(L, -1);
|
|
|
|
if (!msg)
|
|
|
|
{
|
|
|
|
msg = "tostring didn't return a string";
|
|
|
|
ok = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!ok)
|
|
|
|
{
|
|
|
|
lua_pushfstring(L, "(invalid error: %s)", msg);
|
|
|
|
lua_remove(L, -2);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-05-02 02:50:05 -06:00
|
|
|
static void report_error(lua_State *L, color_ostream *out = NULL, bool pop = false)
|
2012-04-04 03:34:07 -06:00
|
|
|
{
|
2012-04-07 04:21:38 -06:00
|
|
|
error_tostring(L, true);
|
2012-04-04 03:34:07 -06:00
|
|
|
|
|
|
|
const char *msg = lua_tostring(L, -1);
|
|
|
|
assert(msg);
|
|
|
|
|
|
|
|
if (out)
|
|
|
|
out->printerr("%s\n", msg);
|
|
|
|
else
|
|
|
|
dfhack_printerr(L, msg);
|
|
|
|
|
2012-05-02 02:50:05 -06:00
|
|
|
lua_pop(L, pop?2:1);
|
2012-04-04 03:34:07 -06:00
|
|
|
}
|
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
static bool convert_to_exception(lua_State *L, int slevel, lua_State *thread = NULL)
|
2012-04-04 03:34:07 -06:00
|
|
|
{
|
2012-04-16 04:45:04 -06:00
|
|
|
if (!thread)
|
|
|
|
thread = L;
|
|
|
|
|
|
|
|
if (thread == L)
|
|
|
|
lua_pushthread(L);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lua_pushthread(thread);
|
|
|
|
lua_xmove(thread, L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_swap(L);
|
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
int base = lua_gettop(L);
|
|
|
|
|
|
|
|
bool force_unknown = false;
|
|
|
|
|
|
|
|
if (lua_istable(L, base) && lua_getmetatable(L, base))
|
|
|
|
{
|
|
|
|
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
|
|
|
|
bool is_exception = lua_rawequal(L, -1, -2);
|
|
|
|
lua_settop(L, base);
|
|
|
|
|
|
|
|
if (is_exception)
|
2012-04-16 04:45:04 -06:00
|
|
|
{
|
|
|
|
// If it is an exception from the same thread, return as is
|
|
|
|
lua_getfield(L, base, "thread");
|
|
|
|
bool same_thread = lua_rawequal(L, -1, base-1);
|
|
|
|
lua_settop(L, base);
|
|
|
|
|
|
|
|
if (same_thread)
|
|
|
|
{
|
|
|
|
lua_remove(L, base-1);
|
|
|
|
return false;
|
|
|
|
}
|
2012-04-04 03:34:07 -06:00
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
// Create a new exception for this thread
|
|
|
|
lua_newtable(L);
|
2012-06-22 06:36:50 -06:00
|
|
|
luaL_where(L, slevel);
|
|
|
|
lua_setfield(L, -2, "where");
|
2012-04-16 04:45:04 -06:00
|
|
|
lua_pushstring(L, "coroutine resume failed");
|
|
|
|
lua_setfield(L, -2, "message");
|
2012-06-22 06:36:50 -06:00
|
|
|
lua_getfield(L, -2, "verbose");
|
|
|
|
lua_setfield(L, -2, "verbose");
|
2012-04-16 04:45:04 -06:00
|
|
|
lua_swap(L);
|
|
|
|
lua_setfield(L, -2, "cause");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
force_unknown = true;
|
2012-03-31 05:40:54 -06:00
|
|
|
}
|
2012-04-04 03:34:07 -06:00
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
// Promote non-table to table, and do some sanity checks
|
2012-04-04 03:34:07 -06:00
|
|
|
if (!lua_istable(L, base) || force_unknown)
|
|
|
|
{
|
|
|
|
lua_newtable(L);
|
|
|
|
lua_swap(L);
|
|
|
|
|
|
|
|
if (lua_isstring(L, -1))
|
|
|
|
lua_setfield(L, base, "message");
|
|
|
|
else
|
|
|
|
{
|
2012-04-07 04:21:38 -06:00
|
|
|
error_tostring(L, true);
|
2012-04-04 03:34:07 -06:00
|
|
|
lua_setfield(L, base, "message");
|
|
|
|
lua_setfield(L, base, "object");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lua_getfield(L, base, "message");
|
|
|
|
|
|
|
|
if (!lua_isstring(L, -1))
|
|
|
|
{
|
|
|
|
error_tostring(L);
|
|
|
|
lua_setfield(L, base, "message");
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_settop(L, base);
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
|
|
|
|
lua_setmetatable(L, base);
|
2012-04-16 04:45:04 -06:00
|
|
|
lua_swap(L);
|
|
|
|
lua_setfield(L, -2, "thread");
|
|
|
|
luaL_traceback(L, thread, NULL, slevel);
|
|
|
|
lua_setfield(L, -2, "stacktrace");
|
2012-04-04 03:34:07 -06:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_onerror(lua_State *L)
|
|
|
|
{
|
|
|
|
luaL_checkany(L, 1);
|
|
|
|
lua_settop(L, 1);
|
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
convert_to_exception(L, 1);
|
2012-04-04 03:34:07 -06:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-06-22 06:36:50 -06:00
|
|
|
static int dfhack_error(lua_State *L)
|
|
|
|
{
|
|
|
|
luaL_checkany(L, 1);
|
|
|
|
lua_settop(L, 3);
|
|
|
|
int level = std::max(1, luaL_optint(L, 2, 1));
|
|
|
|
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
|
|
|
|
if (convert_to_exception(L, level))
|
|
|
|
{
|
|
|
|
luaL_where(L, level);
|
|
|
|
lua_setfield(L, -2, "where");
|
|
|
|
|
|
|
|
if (!lua_isnil(L, 3))
|
|
|
|
{
|
|
|
|
lua_pushvalue(L, 3);
|
|
|
|
lua_setfield(L, -2, "verbose");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return lua_error(L);
|
|
|
|
}
|
|
|
|
|
2021-02-27 22:38:59 -07:00
|
|
|
// replaces os.exit with a thrown exception
|
|
|
|
static int dfhack_exit_error(lua_State *L)
|
|
|
|
{
|
|
|
|
int exit_code = luaL_checkint(L, 1);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
lua_pushfstring(L,
|
|
|
|
"prevented execution of os.exit(%d); please use error()"
|
|
|
|
" or qerror() instead.", exit_code);
|
|
|
|
return dfhack_error(L);
|
|
|
|
}
|
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
static int dfhack_exception_tostring(lua_State *L)
|
|
|
|
{
|
|
|
|
luaL_checktype(L, 1, LUA_TTABLE);
|
2012-06-22 06:36:50 -06:00
|
|
|
lua_settop(L, 2);
|
|
|
|
|
|
|
|
if (lua_isnil(L, 2))
|
|
|
|
{
|
|
|
|
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
|
|
|
|
lua_getfield(L, -1, "verbose");
|
|
|
|
lua_insert(L, 2);
|
|
|
|
lua_settop(L, 2);
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_getfield(L, 1, "verbose");
|
|
|
|
|
|
|
|
bool verbose =
|
|
|
|
lua_toboolean(L, 2) || lua_toboolean(L, 3) ||
|
|
|
|
(lua_isnil(L, 2) && lua_isnil(L, 3));
|
2012-04-04 03:34:07 -06:00
|
|
|
|
|
|
|
int base = lua_gettop(L);
|
|
|
|
|
2012-06-22 06:36:50 -06:00
|
|
|
if (verbose || lua_isnil(L, 3))
|
|
|
|
{
|
|
|
|
lua_getfield(L, 1, "where");
|
|
|
|
if (!lua_isstring(L, -1))
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
lua_getfield(L, 1, "message");
|
|
|
|
if (!lua_isstring(L, -1))
|
|
|
|
{
|
|
|
|
lua_pop(L, 1);
|
|
|
|
lua_pushstring(L, "(error message is not a string)");
|
|
|
|
}
|
|
|
|
|
2012-06-22 06:36:50 -06:00
|
|
|
if (verbose)
|
|
|
|
{
|
|
|
|
lua_pushstring(L, "\n");
|
|
|
|
lua_getfield(L, 1, "stacktrace");
|
|
|
|
if (!lua_isstring(L, -1))
|
|
|
|
lua_pop(L, 2);
|
|
|
|
}
|
2012-04-04 03:34:07 -06:00
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
lua_pushstring(L, "\ncaused by:\n");
|
|
|
|
lua_getfield(L, 1, "cause");
|
|
|
|
if (lua_isnil(L, -1))
|
|
|
|
lua_pop(L, 2);
|
2012-06-22 06:36:50 -06:00
|
|
|
else if (lua_istable(L, -1))
|
|
|
|
{
|
|
|
|
lua_pushcfunction(L, dfhack_exception_tostring);
|
|
|
|
lua_swap(L);
|
|
|
|
lua_pushvalue(L, 2);
|
|
|
|
if (lua_pcall(L, 2, 1, 0) != LUA_OK)
|
|
|
|
error_tostring(L);
|
|
|
|
}
|
2012-04-07 04:21:38 -06:00
|
|
|
else
|
|
|
|
error_tostring(L);
|
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
lua_concat(L, lua_gettop(L) - base);
|
2012-03-31 05:40:54 -06:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
static void push_simple_error(lua_State *L, const char *str)
|
2012-04-03 10:02:01 -06:00
|
|
|
{
|
2012-04-07 04:21:38 -06:00
|
|
|
lua_pushstring(L, str);
|
|
|
|
|
|
|
|
if (lua_checkstack(L, LUA_MINSTACK))
|
2012-04-16 04:45:04 -06:00
|
|
|
convert_to_exception(L, 0);
|
2012-04-07 04:21:38 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space = 2)
|
|
|
|
{
|
|
|
|
if (!lua_checkstack(L, space))
|
2012-04-03 10:02:01 -06:00
|
|
|
{
|
2012-04-07 04:21:38 -06:00
|
|
|
lua_settop(L, base-1); /* create space for return values */
|
2012-04-03 10:02:01 -06:00
|
|
|
lua_pushboolean(L, 0);
|
2012-04-07 04:21:38 -06:00
|
|
|
push_simple_error(L, "stack overflow");
|
|
|
|
return false;
|
2012-04-03 10:02:01 -06:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lua_pushboolean(L, success);
|
2012-04-07 04:21:38 -06:00
|
|
|
lua_replace(L, base); /* put first result in first slot */
|
2012-04-15 11:50:22 -06:00
|
|
|
return success;
|
2012-04-03 10:02:01 -06:00
|
|
|
}
|
2012-04-07 04:21:38 -06:00
|
|
|
}
|
|
|
|
|
2012-04-16 08:01:21 -06:00
|
|
|
namespace {
|
2016-07-26 21:47:53 -06:00
|
|
|
int safecall_cont(lua_State *L, int status, lua_KContext)
|
2012-04-16 08:01:21 -06:00
|
|
|
{
|
|
|
|
bool success = do_finish_pcall(L, Lua::IsSuccess(status));
|
2012-04-03 10:02:01 -06:00
|
|
|
|
2012-04-16 08:01:21 -06:00
|
|
|
if (!success)
|
|
|
|
report_error(L);
|
2012-04-03 10:02:01 -06:00
|
|
|
|
2012-04-16 08:01:21 -06:00
|
|
|
return lua_gettop(L);
|
|
|
|
}
|
2012-04-03 10:02:01 -06:00
|
|
|
}
|
|
|
|
|
2012-04-16 08:01:21 -06:00
|
|
|
static int dfhack_safecall (lua_State *L)
|
2012-04-03 10:02:01 -06:00
|
|
|
{
|
|
|
|
luaL_checkany(L, 1);
|
2012-04-04 03:34:07 -06:00
|
|
|
lua_pushcfunction(L, dfhack_onerror);
|
2012-04-03 10:02:01 -06:00
|
|
|
lua_insert(L, 1);
|
2012-04-16 08:01:21 -06:00
|
|
|
return Lua::TailPCallK<safecall_cont>(L, lua_gettop(L) - 2, LUA_MULTRET, 1, 0);
|
2012-04-03 10:02:01 -06:00
|
|
|
}
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
bool DFHack::Lua::SafeCall(color_ostream &out, lua_State *L, int nargs, int nres, bool perr)
|
|
|
|
{
|
2012-04-15 09:09:25 -06:00
|
|
|
AssertCoreSuspend(L);
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
int base = lua_gettop(L) - nargs;
|
|
|
|
|
|
|
|
color_ostream *cur_out = Lua::GetOutput(L);
|
|
|
|
set_dfhack_output(L, &out);
|
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
lua_pushcfunction(L, dfhack_onerror);
|
2012-03-31 05:40:54 -06:00
|
|
|
lua_insert(L, base);
|
|
|
|
|
|
|
|
bool ok = lua_pcall(L, nargs, nres, base) == LUA_OK;
|
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
if (!ok && perr)
|
2012-05-02 02:50:05 -06:00
|
|
|
report_error(L, &out, true);
|
2012-04-04 03:34:07 -06:00
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
lua_remove(L, base);
|
|
|
|
set_dfhack_output(L, cur_out);
|
|
|
|
|
|
|
|
return ok;
|
|
|
|
}
|
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
// Copied from lcorolib.c, with error handling modifications
|
|
|
|
static int resume_helper(lua_State *L, lua_State *co, int narg, int nres)
|
|
|
|
{
|
|
|
|
if (!co) {
|
|
|
|
lua_pop(L, narg);
|
|
|
|
push_simple_error(L, "coroutine expected in resume");
|
|
|
|
return LUA_ERRRUN;
|
|
|
|
}
|
|
|
|
if (!lua_checkstack(co, narg)) {
|
|
|
|
lua_pop(L, narg);
|
|
|
|
push_simple_error(L, "too many arguments to resume");
|
|
|
|
return LUA_ERRRUN;
|
|
|
|
}
|
|
|
|
if (lua_status(co) == LUA_OK && lua_gettop(co) == 0) {
|
|
|
|
lua_pop(L, narg);
|
|
|
|
push_simple_error(L, "cannot resume dead coroutine");
|
|
|
|
return LUA_ERRRUN;
|
|
|
|
}
|
|
|
|
lua_xmove(L, co, narg);
|
|
|
|
int status = lua_resume(co, L, narg);
|
2012-04-16 08:01:21 -06:00
|
|
|
if (Lua::IsSuccess(status))
|
2012-04-16 04:45:04 -06:00
|
|
|
{
|
|
|
|
int nact = lua_gettop(co);
|
|
|
|
if (nres == LUA_MULTRET)
|
|
|
|
nres = nact;
|
|
|
|
else if (nres < nact)
|
|
|
|
lua_settop(co, nact = nres);
|
|
|
|
if (!lua_checkstack(L, nres + 1)) {
|
|
|
|
lua_settop(co, 0);
|
|
|
|
push_simple_error(L, "too many results to resume");
|
|
|
|
return LUA_ERRRUN;
|
|
|
|
}
|
|
|
|
int ttop = lua_gettop(L) + nres;
|
|
|
|
lua_xmove(co, L, nact);
|
|
|
|
lua_settop(L, ttop);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lua_xmove(co, L, 1);
|
|
|
|
|
|
|
|
// A cross-thread version of dfhack_onerror
|
|
|
|
if (lua_checkstack(L, LUA_MINSTACK))
|
|
|
|
convert_to_exception(L, 0, co);
|
|
|
|
}
|
|
|
|
return status;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_coresume (lua_State *L) {
|
|
|
|
lua_State *co = lua_tothread(L, 1);
|
|
|
|
luaL_argcheck(L, !!co, 1, "coroutine expected");
|
|
|
|
int r = resume_helper(L, co, lua_gettop(L) - 1, LUA_MULTRET);
|
2012-04-16 08:01:21 -06:00
|
|
|
bool ok = Lua::IsSuccess(r);
|
2012-04-16 04:45:04 -06:00
|
|
|
lua_pushboolean(L, ok);
|
|
|
|
lua_insert(L, 2);
|
|
|
|
return lua_gettop(L) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_saferesume (lua_State *L) {
|
|
|
|
lua_State *co = lua_tothread(L, 1);
|
|
|
|
luaL_argcheck(L, !!co, 1, "coroutine expected");
|
|
|
|
int r = resume_helper(L, co, lua_gettop(L) - 1, LUA_MULTRET);
|
2012-04-16 08:01:21 -06:00
|
|
|
bool ok = Lua::IsSuccess(r);
|
2012-04-16 04:45:04 -06:00
|
|
|
lua_pushboolean(L, ok);
|
|
|
|
lua_insert(L, 2);
|
|
|
|
if (!ok)
|
|
|
|
report_error(L);
|
|
|
|
return lua_gettop(L) - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_coauxwrap (lua_State *L) {
|
|
|
|
lua_State *co = lua_tothread(L, lua_upvalueindex(1));
|
|
|
|
int r = resume_helper(L, co, lua_gettop(L), LUA_MULTRET);
|
2012-04-16 08:01:21 -06:00
|
|
|
if (Lua::IsSuccess(r))
|
2012-04-16 04:45:04 -06:00
|
|
|
return lua_gettop(L);
|
|
|
|
else
|
2012-06-22 06:36:50 -06:00
|
|
|
{
|
|
|
|
if (lua_checkstack(L, LUA_MINSTACK))
|
|
|
|
convert_to_exception(L, 1);
|
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
return lua_error(L);
|
2012-06-22 06:36:50 -06:00
|
|
|
}
|
2012-04-16 04:45:04 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_cowrap (lua_State *L) {
|
|
|
|
luaL_checktype(L, 1, LUA_TFUNCTION);
|
|
|
|
lua_settop(L, 1);
|
|
|
|
Lua::NewCoroutine(L);
|
|
|
|
lua_pushcclosure(L, dfhack_coauxwrap, 1);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2021-02-27 22:38:59 -07:00
|
|
|
static void add_luacov_coroutine_wrapper(lua_State *L)
|
|
|
|
{
|
|
|
|
color_ostream *out = Lua::GetOutput(L);
|
|
|
|
if (!Lua::PushModulePublic(*out, L, "luacov_helper", "with_luacov"))
|
|
|
|
{
|
|
|
|
out->printerr("Failed to load luacov_helper.with_luacov.\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
lua_swap(L);
|
|
|
|
if (!Lua::SafeCall(*out, L, 1, 1))
|
|
|
|
out->printerr("Failed to set luacov monitoring wrapper.\n");
|
|
|
|
}
|
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
lua_State *DFHack::Lua::NewCoroutine(lua_State *L) {
|
2021-03-11 00:31:30 -07:00
|
|
|
if (Lua::IsCoreContext(L) && getenv("DFHACK_ENABLE_LUACOV"))
|
2021-02-27 22:38:59 -07:00
|
|
|
add_luacov_coroutine_wrapper(L);
|
2012-04-16 04:45:04 -06:00
|
|
|
lua_State *NL = lua_newthread(L);
|
|
|
|
lua_swap(L);
|
|
|
|
lua_xmove(L, NL, 1); /* move function from L to NL */
|
|
|
|
return NL;
|
|
|
|
}
|
|
|
|
|
|
|
|
int DFHack::Lua::SafeResume(color_ostream &out, lua_State *from, lua_State *thread, int nargs, int nres, bool perr)
|
|
|
|
{
|
|
|
|
AssertCoreSuspend(from);
|
|
|
|
|
|
|
|
color_ostream *cur_out = Lua::GetOutput(from);
|
|
|
|
set_dfhack_output(from, &out);
|
|
|
|
|
|
|
|
int rv = resume_helper(from, thread, nargs, nres);
|
|
|
|
|
2012-04-16 08:01:21 -06:00
|
|
|
if (!Lua::IsSuccess(rv) && perr)
|
2012-05-02 02:50:05 -06:00
|
|
|
report_error(from, &out, true);
|
2012-04-16 04:45:04 -06:00
|
|
|
|
|
|
|
set_dfhack_output(from, cur_out);
|
|
|
|
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
int DFHack::Lua::SafeResume(color_ostream &out, lua_State *from, int nargs, int nres, bool perr)
|
|
|
|
{
|
|
|
|
int base = lua_gettop(from) - nargs;
|
|
|
|
lua_State *thread = lua_tothread(from, base);
|
|
|
|
|
|
|
|
int rv = SafeResume(out, from, thread, nargs, nres, perr);
|
|
|
|
|
|
|
|
lua_remove(from, base);
|
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Module loading
|
|
|
|
*/
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
static int DFHACK_LOADED_TOKEN = 0;
|
2012-04-21 10:15:57 -06:00
|
|
|
static int DFHACK_DFHACK_TOKEN = 0;
|
|
|
|
static int DFHACK_BASE_G_TOKEN = 0;
|
|
|
|
static int DFHACK_REQUIRE_TOKEN = 0;
|
2012-04-15 09:09:25 -06:00
|
|
|
|
2012-05-04 09:47:18 -06:00
|
|
|
void Lua::PushDFHack(lua_State *state)
|
|
|
|
{
|
|
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Lua::PushBaseGlobals(lua_State *state)
|
|
|
|
{
|
|
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN);
|
|
|
|
}
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
bool DFHack::Lua::PushModule(color_ostream &out, lua_State *state, const char *module)
|
2012-03-31 05:40:54 -06:00
|
|
|
{
|
2012-04-15 09:09:25 -06:00
|
|
|
AssertCoreSuspend(state);
|
|
|
|
|
|
|
|
// Check if it is already loaded
|
|
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_LOADED_TOKEN);
|
|
|
|
lua_pushstring(state, module);
|
|
|
|
lua_rawget(state, -2);
|
|
|
|
|
|
|
|
if (lua_toboolean(state, -1))
|
|
|
|
{
|
|
|
|
lua_remove(state, -2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_pop(state, 2);
|
2012-04-21 10:15:57 -06:00
|
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_REQUIRE_TOKEN);
|
2012-04-15 09:09:25 -06:00
|
|
|
lua_pushstring(state, module);
|
|
|
|
|
|
|
|
return Lua::SafeCall(out, state, 1, 1);
|
|
|
|
}
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
bool DFHack::Lua::PushModulePublic(color_ostream &out, lua_State *state,
|
|
|
|
const char *module, const char *name)
|
|
|
|
{
|
|
|
|
if (!PushModule(out, state, module))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
if (!lua_istable(state, -1))
|
|
|
|
{
|
|
|
|
lua_pop(state, 1);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_pushstring(state, name);
|
|
|
|
lua_rawget(state, -2);
|
|
|
|
lua_remove(state, -2);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DFHack::Lua::Require(color_ostream &out, lua_State *state,
|
|
|
|
const std::string &module, bool setglobal)
|
|
|
|
{
|
|
|
|
if (!PushModule(out, state, module.c_str()))
|
2012-03-31 05:40:54 -06:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if (setglobal)
|
2012-04-21 10:15:57 -06:00
|
|
|
{
|
|
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN);
|
|
|
|
lua_swap(state);
|
|
|
|
lua_setfield(state, -2, module.c_str());
|
|
|
|
}
|
2012-03-31 05:40:54 -06:00
|
|
|
else
|
|
|
|
lua_pop(state, 1);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-05-02 02:50:05 -06:00
|
|
|
static bool doAssignDFObject(color_ostream *out, lua_State *state,
|
|
|
|
type_identity *type, void *target, int val_index,
|
|
|
|
bool exact, bool perr, bool signal)
|
|
|
|
{
|
|
|
|
if (signal)
|
|
|
|
check_valid_ptr_index(state, val_index);
|
|
|
|
|
|
|
|
if (lua_istable(state, val_index))
|
|
|
|
{
|
|
|
|
val_index = lua_absindex(state, val_index);
|
|
|
|
lua_getfield(state, LUA_REGISTRYINDEX, DFHACK_ASSIGN_NAME);
|
|
|
|
Lua::PushDFObject(state, type, target);
|
|
|
|
lua_pushvalue(state, val_index);
|
|
|
|
|
|
|
|
if (signal)
|
|
|
|
{
|
|
|
|
lua_call(state, 2, 0);
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
return Lua::SafeCall(*out, state, 2, 0, perr);
|
|
|
|
}
|
|
|
|
else if (!lua_isuserdata(state, val_index))
|
|
|
|
{
|
|
|
|
signal_typeid_error(out, state, type, "pointer to %s expected",
|
|
|
|
val_index, perr, signal);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
void *in_ptr = Lua::GetDFObject(state, type, val_index, exact);
|
|
|
|
if (!in_ptr)
|
|
|
|
{
|
|
|
|
signal_typeid_error(out, state, type, "incompatible pointer type: %s expected",
|
|
|
|
val_index, perr, signal);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
if (!type->copy(target, in_ptr))
|
|
|
|
{
|
|
|
|
signal_typeid_error(out, state, type, "no copy support for %s",
|
|
|
|
val_index, perr, signal);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-04-03 10:02:01 -06:00
|
|
|
bool DFHack::Lua::AssignDFObject(color_ostream &out, lua_State *state,
|
2012-05-02 02:50:05 -06:00
|
|
|
type_identity *type, void *target, int val_index,
|
|
|
|
bool exact_type, bool perr)
|
2012-03-31 05:40:54 -06:00
|
|
|
{
|
2012-05-02 02:50:05 -06:00
|
|
|
return doAssignDFObject(&out, state, type, target, val_index, exact_type, perr, false);
|
|
|
|
}
|
|
|
|
|
2012-05-05 10:46:28 -06:00
|
|
|
void DFHack::Lua::CheckDFAssign(lua_State *state, type_identity *type,
|
2012-05-02 02:50:05 -06:00
|
|
|
void *target, int val_index, bool exact_type)
|
|
|
|
{
|
|
|
|
doAssignDFObject(NULL, state, type, target, val_index, exact_type, false, true);
|
2012-04-03 10:02:01 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
bool DFHack::Lua::SafeCallString(color_ostream &out, lua_State *state, const std::string &code,
|
|
|
|
int nargs, int nres, bool perr,
|
|
|
|
const char *debug_tag, int env_idx)
|
|
|
|
{
|
2012-04-15 09:09:25 -06:00
|
|
|
AssertCoreSuspend(state);
|
|
|
|
|
2012-04-03 10:02:01 -06:00
|
|
|
if (!debug_tag)
|
|
|
|
debug_tag = code.c_str();
|
|
|
|
if (env_idx)
|
|
|
|
env_idx = lua_absindex(state, env_idx);
|
|
|
|
|
|
|
|
int base = lua_gettop(state);
|
2016-10-14 22:37:18 -06:00
|
|
|
(void)base; // used in assert()
|
2012-04-03 10:02:01 -06:00
|
|
|
|
|
|
|
// Parse the code
|
|
|
|
if (luaL_loadbuffer(state, code.data(), code.size(), debug_tag) != LUA_OK)
|
2012-03-31 05:40:54 -06:00
|
|
|
{
|
2012-04-03 10:02:01 -06:00
|
|
|
if (perr)
|
2012-05-02 02:50:05 -06:00
|
|
|
report_error(state, &out, true);
|
2012-04-03 10:02:01 -06:00
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Replace _ENV
|
2012-04-03 10:02:01 -06:00
|
|
|
if (env_idx)
|
2012-03-31 05:40:54 -06:00
|
|
|
{
|
2012-04-03 10:02:01 -06:00
|
|
|
lua_pushvalue(state, env_idx);
|
|
|
|
lua_setupvalue(state, -2, 1);
|
|
|
|
assert(lua_gettop(state) == base+1);
|
2012-03-31 05:40:54 -06:00
|
|
|
}
|
|
|
|
|
2012-04-03 10:02:01 -06:00
|
|
|
if (nargs > 0)
|
|
|
|
lua_insert(state, -1-nargs);
|
|
|
|
|
|
|
|
return Lua::SafeCall(out, state, nargs, nres, perr);
|
2012-03-31 05:40:54 -06:00
|
|
|
}
|
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
/*
|
|
|
|
* Coroutine interactive query loop
|
|
|
|
*/
|
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
static int resume_query_loop(color_ostream &out,
|
2012-04-16 04:45:04 -06:00
|
|
|
lua_State *thread, lua_State *state, int nargs,
|
2012-04-15 11:50:22 -06:00
|
|
|
std::string &prompt, std::string &histfile)
|
2012-03-31 05:40:54 -06:00
|
|
|
{
|
2012-04-16 04:45:04 -06:00
|
|
|
int rv = Lua::SafeResume(out, state, thread, nargs, 2);
|
2012-04-15 11:50:22 -06:00
|
|
|
|
2012-04-16 08:01:21 -06:00
|
|
|
if (Lua::IsSuccess(rv))
|
2012-04-15 11:50:22 -06:00
|
|
|
{
|
2012-04-16 04:45:04 -06:00
|
|
|
prompt = ifnull(lua_tostring(state, -2), "");
|
|
|
|
histfile = ifnull(lua_tostring(state, -1), "");
|
|
|
|
lua_pop(state, 2);
|
2012-04-15 11:50:22 -06:00
|
|
|
}
|
2012-04-15 09:09:25 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
return rv;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool DFHack::Lua::RunCoreQueryLoop(color_ostream &out, lua_State *state,
|
2012-04-16 04:45:04 -06:00
|
|
|
bool (*init)(color_ostream&, lua_State*, void*),
|
2012-04-15 11:50:22 -06:00
|
|
|
void *arg)
|
|
|
|
{
|
2012-03-31 05:40:54 -06:00
|
|
|
if (!lua_checkstack(state, 20))
|
|
|
|
return false;
|
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
lua_State *thread;
|
|
|
|
int rv;
|
|
|
|
std::string prompt;
|
|
|
|
std::string histfile;
|
2012-03-31 05:40:54 -06:00
|
|
|
|
|
|
|
DFHack::CommandHistory hist;
|
2012-04-15 11:50:22 -06:00
|
|
|
std::string histname;
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
{
|
|
|
|
CoreSuspender suspend;
|
2012-04-01 07:00:25 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
int base = lua_gettop(state);
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
if (!init(out, state, arg))
|
2012-04-15 11:50:22 -06:00
|
|
|
{
|
|
|
|
lua_settop(state, base);
|
|
|
|
return false;
|
|
|
|
}
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-05-04 09:47:18 -06:00
|
|
|
// If not interactive, run without coroutine and bail out
|
|
|
|
if (!out.is_console())
|
|
|
|
return SafeCall(out, state, lua_gettop(state)-base-1, 0);
|
|
|
|
|
2012-04-16 08:05:42 -06:00
|
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN);
|
2012-04-16 04:45:04 -06:00
|
|
|
lua_pushvalue(state, base+1);
|
|
|
|
lua_remove(state, base+1);
|
|
|
|
thread = Lua::NewCoroutine(state);
|
2012-04-16 08:05:42 -06:00
|
|
|
lua_rawsetp(state, -2, thread);
|
|
|
|
lua_pop(state, 1);
|
2012-04-03 03:13:44 -06:00
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
rv = resume_query_loop(out, thread, state, lua_gettop(state)-base, prompt, histfile);
|
2012-04-15 11:50:22 -06:00
|
|
|
}
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-05-04 09:47:18 -06:00
|
|
|
Console &con = static_cast<Console&>(out);
|
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
while (rv == LUA_YIELD)
|
|
|
|
{
|
|
|
|
if (histfile != histname)
|
|
|
|
{
|
|
|
|
if (!histname.empty())
|
|
|
|
hist.save(histname.c_str());
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
hist.clear();
|
|
|
|
histname = histfile;
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
if (!histname.empty())
|
|
|
|
hist.load(histname.c_str());
|
|
|
|
}
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
if (prompt.empty())
|
|
|
|
prompt = ">> ";
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
std::string curline;
|
2018-07-04 06:21:25 -06:00
|
|
|
while((rv = con.lineedit(prompt,curline,hist)) == Console::RETRY);
|
|
|
|
if (rv <= Console::FAILURE) {
|
|
|
|
rv = rv == Console::SHUTDOWN ? LUA_OK : LUA_ERRRUN;
|
2018-06-21 07:57:36 -06:00
|
|
|
break;
|
|
|
|
}
|
2012-03-31 05:40:54 -06:00
|
|
|
hist.add(curline);
|
|
|
|
|
|
|
|
{
|
2012-04-15 11:50:22 -06:00
|
|
|
CoreSuspender suspend;
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
lua_pushlstring(state, curline.data(), curline.size());
|
|
|
|
rv = resume_query_loop(out, thread, state, 1, prompt, histfile);
|
2012-04-15 11:50:22 -06:00
|
|
|
}
|
|
|
|
}
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
if (!histname.empty())
|
|
|
|
hist.save(histname.c_str());
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
{
|
|
|
|
CoreSuspender suspend;
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-16 08:05:42 -06:00
|
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN);
|
2012-04-15 11:50:22 -06:00
|
|
|
lua_pushnil(state);
|
2012-04-16 08:05:42 -06:00
|
|
|
lua_rawsetp(state, -2, thread);
|
|
|
|
lua_pop(state, 1);
|
2012-03-31 05:40:54 -06:00
|
|
|
}
|
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
return (rv == LUA_OK);
|
|
|
|
}
|
|
|
|
|
|
|
|
namespace {
|
|
|
|
struct InterpreterArgs {
|
|
|
|
const char *prompt;
|
|
|
|
const char *hfile;
|
|
|
|
};
|
|
|
|
}
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
static bool init_interpreter(color_ostream &out, lua_State *state, void *info)
|
2012-04-15 11:50:22 -06:00
|
|
|
{
|
|
|
|
auto args = (InterpreterArgs*)info;
|
2012-04-21 10:15:57 -06:00
|
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN);
|
2012-04-15 11:50:22 -06:00
|
|
|
lua_getfield(state, -1, "interpreter");
|
2012-04-16 04:45:04 -06:00
|
|
|
lua_remove(state, -2);
|
2012-04-15 11:50:22 -06:00
|
|
|
lua_pushstring(state, args->prompt);
|
|
|
|
lua_pushstring(state, args->hfile);
|
2012-03-31 05:40:54 -06:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
bool DFHack::Lua::InterpreterLoop(color_ostream &out, lua_State *state,
|
|
|
|
const char *prompt, const char *hfile)
|
2012-03-31 05:40:54 -06:00
|
|
|
{
|
2012-04-15 11:50:22 -06:00
|
|
|
if (!out.is_console())
|
|
|
|
return false;
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
if (!hfile)
|
2022-07-23 23:12:34 -06:00
|
|
|
hfile = "dfhack-config/lua.history";
|
2012-04-15 11:50:22 -06:00
|
|
|
if (!prompt)
|
|
|
|
prompt = "lua";
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
InterpreterArgs args;
|
|
|
|
args.prompt = prompt;
|
|
|
|
args.hfile = hfile;
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 11:50:22 -06:00
|
|
|
return RunCoreQueryLoop(out, state, init_interpreter, &args);
|
2012-03-31 05:40:54 -06:00
|
|
|
}
|
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
static bool do_invoke_cleanup(lua_State *L, int nargs, int errorfun, bool success)
|
2012-04-01 09:38:42 -06:00
|
|
|
{
|
2012-04-07 04:21:38 -06:00
|
|
|
bool ok = lua_pcall(L, nargs, 0, errorfun) == LUA_OK;
|
2012-04-01 09:38:42 -06:00
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
if (!ok)
|
2012-04-01 09:38:42 -06:00
|
|
|
{
|
2012-04-07 04:21:38 -06:00
|
|
|
// If finalization failed, attach the previous error
|
|
|
|
if (lua_istable(L, -1) && !success)
|
|
|
|
{
|
|
|
|
lua_swap(L);
|
|
|
|
lua_setfield(L, -2, "cause");
|
|
|
|
}
|
2012-04-01 09:38:42 -06:00
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
success = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return success;
|
|
|
|
}
|
2012-04-01 09:38:42 -06:00
|
|
|
|
2016-07-26 21:47:53 -06:00
|
|
|
int dfhack_cleanup_cont(lua_State *L, int status, lua_KContext)
|
2012-04-07 04:21:38 -06:00
|
|
|
{
|
2012-04-16 08:01:21 -06:00
|
|
|
bool success = Lua::IsSuccess(status);
|
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
int nargs = lua_tointeger(L, 1);
|
|
|
|
bool always = lua_toboolean(L, 2);
|
|
|
|
int rvbase = 4+nargs;
|
|
|
|
|
|
|
|
// stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [rvals/error...]
|
2012-04-01 09:38:42 -06:00
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
int numret = lua_gettop(L) - rvbase;
|
|
|
|
|
|
|
|
if (!success || always)
|
|
|
|
{
|
|
|
|
if (numret > 0)
|
|
|
|
{
|
|
|
|
if (numret == 1)
|
|
|
|
{
|
|
|
|
// Inject the only result instead of pulling cleanup args
|
|
|
|
lua_insert(L, 4);
|
|
|
|
}
|
|
|
|
else if (!lua_checkstack(L, nargs+1))
|
|
|
|
{
|
|
|
|
success = false;
|
|
|
|
lua_settop(L, rvbase);
|
|
|
|
push_simple_error(L, "stack overflow");
|
|
|
|
lua_insert(L, 4);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
for (int i = 0; i <= nargs; i++)
|
|
|
|
lua_pushvalue(L, 4+i);
|
|
|
|
}
|
|
|
|
}
|
2012-04-01 09:38:42 -06:00
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
success = do_invoke_cleanup(L, nargs, 3, success);
|
2012-04-01 09:38:42 -06:00
|
|
|
}
|
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
if (!success)
|
|
|
|
lua_error(L);
|
|
|
|
|
|
|
|
return numret;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_call_with_finalizer (lua_State *L)
|
|
|
|
{
|
|
|
|
int nargs = luaL_checkint(L, 1);
|
|
|
|
if (nargs < 0)
|
|
|
|
luaL_argerror(L, 1, "invalid cleanup argument count");
|
|
|
|
luaL_checktype(L, 3, LUA_TFUNCTION);
|
2012-04-01 09:38:42 -06:00
|
|
|
|
2012-04-07 04:21:38 -06:00
|
|
|
// Inject errorfun
|
|
|
|
lua_pushcfunction(L, dfhack_onerror);
|
|
|
|
lua_insert(L, 3);
|
|
|
|
|
|
|
|
int rvbase = 4+nargs; // rvbase+1 points to the function argument
|
|
|
|
|
|
|
|
if (lua_gettop(L) < rvbase)
|
|
|
|
luaL_error(L, "not enough arguments even to invoke cleanup");
|
|
|
|
|
|
|
|
// stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [fun] [args...]
|
|
|
|
|
|
|
|
// Not enough stack to call and post-cleanup, or nothing to call?
|
|
|
|
bool no_args = lua_gettop(L) == rvbase;
|
|
|
|
|
|
|
|
if (!lua_checkstack(L, nargs+2) || no_args)
|
|
|
|
{
|
|
|
|
push_simple_error(L, no_args ? "fn argument expected" : "stack overflow");
|
|
|
|
lua_insert(L, 4);
|
|
|
|
|
|
|
|
// stack: ... [errorfun] [error] [cleanup fun] [cleanup args...]
|
|
|
|
do_invoke_cleanup(L, nargs, 3, false);
|
2012-04-01 09:38:42 -06:00
|
|
|
lua_error(L);
|
2012-04-07 04:21:38 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
// Actually invoke
|
|
|
|
|
|
|
|
// stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [fun] [args...]
|
2012-04-16 08:01:21 -06:00
|
|
|
return Lua::TailPCallK<dfhack_cleanup_cont>(L, lua_gettop(L)-rvbase-1, LUA_MULTRET, 3, 0);
|
2012-04-07 04:21:38 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
static int lua_dfhack_with_suspend(lua_State *L)
|
|
|
|
{
|
|
|
|
int nargs = lua_gettop(L);
|
|
|
|
luaL_checktype(L, 1, LUA_TFUNCTION);
|
|
|
|
|
|
|
|
CoreSuspender suspend;
|
|
|
|
lua_call(L, nargs-1, LUA_MULTRET);
|
2012-04-01 09:38:42 -06:00
|
|
|
|
|
|
|
return lua_gettop(L);
|
|
|
|
}
|
|
|
|
|
Allow plugins to export functions to lua with safe reload support.
- To ensure reload safety functions have to be wrapped. Every call
checks the loaded state and locks a mutex in Plugin. If the plugin
is unloaded, calling its functions throws a lua error. Therefore,
plugins may not create closures or export yieldable functions.
- The set of function argument and return types supported by
LuaWrapper is severely limited when compared to being compiled
inside the main library.
Currently supported types: numbers, bool, std::string, df::foo,
df::foo*, std::vector<bool>, std::vector<df::foo*>.
- To facilitate postponing initialization until after all plugins
have been loaded, the core sends a SC_CORE_INITIALIZED event.
- As an example, the burrows plugin now exports its functions.
2012-04-14 09:44:07 -06:00
|
|
|
static int dfhack_open_plugin(lua_State *L)
|
|
|
|
{
|
|
|
|
luaL_checktype(L, 1, LUA_TTABLE);
|
|
|
|
luaL_checktype(L, 2, LUA_TSTRING);
|
|
|
|
const char *name = lua_tostring(L, 2);
|
|
|
|
|
|
|
|
PluginManager *pmgr = Core::getInstance().getPluginManager();
|
|
|
|
Plugin *plugin = pmgr->getPluginByName(name);
|
|
|
|
|
|
|
|
if (!plugin)
|
|
|
|
luaL_error(L, "plugin not found: '%s'", name);
|
|
|
|
|
|
|
|
plugin->open_lua(L, 1);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-07-26 21:47:53 -06:00
|
|
|
static int gettop_wrapper(lua_State *L, int, lua_KContext)
|
|
|
|
{
|
|
|
|
return lua_gettop(L);
|
|
|
|
}
|
|
|
|
|
2012-09-05 07:37:36 -06:00
|
|
|
static int dfhack_curry_wrap(lua_State *L)
|
|
|
|
{
|
|
|
|
int nargs = lua_gettop(L);
|
|
|
|
int ncurry = lua_tointeger(L, lua_upvalueindex(1));
|
|
|
|
int scount = nargs + ncurry;
|
|
|
|
|
|
|
|
luaL_checkstack(L, ncurry, "stack overflow in curry");
|
|
|
|
|
|
|
|
// Insert values in O(N+M) by first shifting the existing data
|
|
|
|
lua_settop(L, scount);
|
|
|
|
for (int i = 0; i < nargs; i++)
|
|
|
|
lua_copy(L, nargs-i, scount-i);
|
|
|
|
for (int i = 1; i <= ncurry; i++)
|
|
|
|
lua_copy(L, lua_upvalueindex(i+1), i);
|
|
|
|
|
2016-07-26 21:47:53 -06:00
|
|
|
lua_callk(L, scount-1, LUA_MULTRET, 0, gettop_wrapper);
|
2012-09-05 07:37:36 -06:00
|
|
|
|
|
|
|
return lua_gettop(L);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_curry(lua_State *L)
|
|
|
|
{
|
|
|
|
luaL_checkany(L, 1);
|
|
|
|
if (lua_isnil(L, 1))
|
|
|
|
luaL_argerror(L, 1, "nil function in curry");
|
|
|
|
if (lua_gettop(L) == 1)
|
|
|
|
return 1;
|
|
|
|
lua_pushinteger(L, lua_gettop(L));
|
|
|
|
lua_insert(L, 1);
|
|
|
|
lua_pushcclosure(L, dfhack_curry_wrap, lua_gettop(L));
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
bool Lua::IsCoreContext(lua_State *state)
|
|
|
|
{
|
|
|
|
// This uses a private field of the lua state to
|
|
|
|
// evaluate the condition without accessing the lua
|
|
|
|
// stack, and thus requiring a lock on the core state.
|
|
|
|
return state && Lua::Core::State &&
|
|
|
|
state->l_G == Lua::Core::State->l_G;
|
|
|
|
}
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
static const luaL_Reg dfhack_funcs[] = {
|
|
|
|
{ "print", lua_dfhack_print },
|
|
|
|
{ "println", lua_dfhack_println },
|
|
|
|
{ "printerr", lua_dfhack_printerr },
|
2012-04-04 00:40:33 -06:00
|
|
|
{ "color", lua_dfhack_color },
|
|
|
|
{ "is_interactive", lua_dfhack_is_interactive },
|
2012-04-16 08:01:21 -06:00
|
|
|
{ "lineedit", dfhack_lineedit },
|
|
|
|
{ "safecall", dfhack_safecall },
|
2012-04-16 04:45:04 -06:00
|
|
|
{ "saferesume", dfhack_saferesume },
|
2012-04-04 03:34:07 -06:00
|
|
|
{ "onerror", dfhack_onerror },
|
2012-06-22 06:36:50 -06:00
|
|
|
{ "error", dfhack_error },
|
2012-04-07 04:21:38 -06:00
|
|
|
{ "call_with_finalizer", dfhack_call_with_finalizer },
|
2012-04-01 09:38:42 -06:00
|
|
|
{ "with_suspend", lua_dfhack_with_suspend },
|
Allow plugins to export functions to lua with safe reload support.
- To ensure reload safety functions have to be wrapped. Every call
checks the loaded state and locks a mutex in Plugin. If the plugin
is unloaded, calling its functions throws a lua error. Therefore,
plugins may not create closures or export yieldable functions.
- The set of function argument and return types supported by
LuaWrapper is severely limited when compared to being compiled
inside the main library.
Currently supported types: numbers, bool, std::string, df::foo,
df::foo*, std::vector<bool>, std::vector<df::foo*>.
- To facilitate postponing initialization until after all plugins
have been loaded, the core sends a SC_CORE_INITIALIZED event.
- As an example, the burrows plugin now exports its functions.
2012-04-14 09:44:07 -06:00
|
|
|
{ "open_plugin", dfhack_open_plugin },
|
2012-09-05 07:37:36 -06:00
|
|
|
{ "curry", dfhack_curry },
|
2012-03-31 05:40:54 -06:00
|
|
|
{ NULL, NULL }
|
|
|
|
};
|
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
static const luaL_Reg dfhack_coro_funcs[] = {
|
|
|
|
{ "resume", dfhack_coresume },
|
|
|
|
{ "wrap", dfhack_cowrap },
|
|
|
|
{ NULL, NULL }
|
|
|
|
};
|
|
|
|
|
2021-02-27 22:38:59 -07:00
|
|
|
static const luaL_Reg dfhack_os_funcs[] = {
|
|
|
|
{ "exit", dfhack_exit_error },
|
|
|
|
{ NULL, NULL }
|
|
|
|
};
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
/************
|
|
|
|
* Events *
|
|
|
|
************/
|
|
|
|
|
|
|
|
static int DFHACK_EVENT_META_TOKEN = 0;
|
|
|
|
|
2012-08-23 09:27:12 -06:00
|
|
|
namespace {
|
|
|
|
struct EventObject {
|
|
|
|
int item_count;
|
|
|
|
Lua::Event::Owner *owner;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
void DFHack::Lua::Event::New(lua_State *state, Owner *owner)
|
2012-04-15 09:09:25 -06:00
|
|
|
{
|
2012-08-23 09:27:12 -06:00
|
|
|
auto obj = (EventObject *)lua_newuserdata(state, sizeof(EventObject));
|
|
|
|
obj->item_count = 0;
|
|
|
|
obj->owner = owner;
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN);
|
|
|
|
lua_setmetatable(state, -2);
|
2012-08-23 09:27:12 -06:00
|
|
|
lua_newtable(state);
|
|
|
|
lua_setuservalue(state, -2);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DFHack::Lua::Event::SetPrivateCallback(lua_State *L, int event)
|
|
|
|
{
|
|
|
|
lua_getuservalue(L, event);
|
|
|
|
lua_swap(L);
|
|
|
|
lua_rawsetp(L, -2, NULL);
|
|
|
|
lua_pop(L, 1);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_event_new(lua_State *L)
|
|
|
|
{
|
|
|
|
Lua::Event::New(L);
|
2012-04-15 09:09:25 -06:00
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-08-23 09:27:12 -06:00
|
|
|
static int dfhack_event_len(lua_State *L)
|
|
|
|
{
|
|
|
|
luaL_checktype(L, 1, LUA_TUSERDATA);
|
|
|
|
auto obj = (EventObject *)lua_touserdata(L, 1);
|
|
|
|
lua_pushinteger(L, obj->item_count);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_event_tostring(lua_State *L)
|
|
|
|
{
|
|
|
|
luaL_checktype(L, 1, LUA_TUSERDATA);
|
|
|
|
auto obj = (EventObject *)lua_touserdata(L, 1);
|
|
|
|
lua_pushfstring(L, "<event: %d listeners>", obj->item_count);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_event_index(lua_State *L)
|
|
|
|
{
|
|
|
|
luaL_checktype(L, 1, LUA_TUSERDATA);
|
|
|
|
lua_getuservalue(L, 1);
|
|
|
|
lua_pushvalue(L, 2);
|
|
|
|
lua_rawget(L, -2);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_event_next(lua_State *L)
|
|
|
|
{
|
|
|
|
luaL_checktype(L, 1, LUA_TUSERDATA);
|
|
|
|
lua_getuservalue(L, 1);
|
|
|
|
lua_pushvalue(L, 2);
|
|
|
|
while (lua_next(L, -2))
|
|
|
|
{
|
|
|
|
if (is_null_userdata(L, -2))
|
|
|
|
lua_pop(L, 1);
|
|
|
|
else
|
|
|
|
return 2;
|
|
|
|
}
|
|
|
|
lua_pushnil(L);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_event_pairs(lua_State *L)
|
|
|
|
{
|
|
|
|
luaL_checktype(L, 1, LUA_TUSERDATA);
|
|
|
|
lua_pushcfunction(L, dfhack_event_next);
|
|
|
|
lua_pushvalue(L, 1);
|
|
|
|
lua_pushnil(L);
|
|
|
|
return 3;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_event_newindex(lua_State *L)
|
|
|
|
{
|
|
|
|
luaL_checktype(L, 1, LUA_TUSERDATA);
|
|
|
|
if (is_null_userdata(L, 2))
|
|
|
|
luaL_argerror(L, 2, "Key NULL is reserved in events.");
|
|
|
|
|
|
|
|
lua_settop(L, 3);
|
|
|
|
lua_getuservalue(L, 1);
|
|
|
|
bool new_nil = lua_isnil(L, 3);
|
|
|
|
|
|
|
|
lua_pushvalue(L, 2);
|
|
|
|
lua_rawget(L, 4);
|
|
|
|
bool old_nil = lua_isnil(L, -1);
|
|
|
|
lua_settop(L, 4);
|
|
|
|
|
|
|
|
lua_pushvalue(L, 2);
|
|
|
|
lua_pushvalue(L, 3);
|
|
|
|
lua_rawset(L, 4);
|
|
|
|
|
|
|
|
int delta = 0;
|
|
|
|
if (old_nil && !new_nil) delta = 1;
|
|
|
|
else if (new_nil && !old_nil) delta = -1;
|
|
|
|
|
|
|
|
if (delta != 0)
|
|
|
|
{
|
|
|
|
auto obj = (EventObject *)lua_touserdata(L, 1);
|
|
|
|
obj->item_count += delta;
|
|
|
|
if (obj->owner)
|
|
|
|
obj->owner->on_count_changed(obj->item_count, delta);
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-04-17 01:45:09 -06:00
|
|
|
static void do_invoke_event(lua_State *L, int argbase, int num_args, int errorfun)
|
|
|
|
{
|
|
|
|
for (int i = 0; i < num_args; i++)
|
|
|
|
lua_pushvalue(L, argbase+i);
|
|
|
|
|
|
|
|
if (lua_pcall(L, num_args, 0, errorfun) != LUA_OK)
|
2012-05-02 02:50:05 -06:00
|
|
|
report_error(L, NULL, true);
|
2012-04-17 01:45:09 -06:00
|
|
|
}
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
static void dfhack_event_invoke(lua_State *L, int base, bool from_c)
|
|
|
|
{
|
|
|
|
int event = base+1;
|
|
|
|
int num_args = lua_gettop(L)-event;
|
|
|
|
|
|
|
|
int errorfun = base+2;
|
|
|
|
lua_pushcfunction(L, dfhack_onerror);
|
|
|
|
lua_insert(L, errorfun);
|
|
|
|
|
|
|
|
int argbase = base+3;
|
|
|
|
|
2012-04-17 01:45:09 -06:00
|
|
|
// stack: |base| event errorfun (args)
|
2012-04-15 09:09:25 -06:00
|
|
|
|
2012-04-17 01:45:09 -06:00
|
|
|
if (!from_c)
|
2012-04-15 09:09:25 -06:00
|
|
|
{
|
2012-04-17 01:45:09 -06:00
|
|
|
// Invoke the NULL key first
|
|
|
|
lua_rawgetp(L, event, NULL);
|
2012-04-15 09:09:25 -06:00
|
|
|
|
2012-04-17 01:45:09 -06:00
|
|
|
if (lua_isnil(L, -1))
|
|
|
|
lua_pop(L, 1);
|
|
|
|
else
|
|
|
|
do_invoke_event(L, argbase, num_args, errorfun);
|
|
|
|
}
|
2012-04-15 09:09:25 -06:00
|
|
|
|
2012-04-17 01:45:09 -06:00
|
|
|
lua_pushnil(L);
|
|
|
|
|
|
|
|
// stack: |base| event errorfun (args) key || cb (args)
|
|
|
|
|
|
|
|
while (lua_next(L, event))
|
|
|
|
{
|
|
|
|
// Skip the NULL key in the main loop
|
2012-08-23 09:27:12 -06:00
|
|
|
if (is_null_userdata(L, -2))
|
2012-04-15 09:09:25 -06:00
|
|
|
lua_pop(L, 1);
|
2012-04-17 01:45:09 -06:00
|
|
|
else
|
|
|
|
do_invoke_event(L, argbase, num_args, errorfun);
|
2012-04-15 09:09:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
lua_settop(L, base);
|
|
|
|
}
|
|
|
|
|
|
|
|
static int dfhack_event_call(lua_State *state)
|
|
|
|
{
|
2012-08-23 09:27:12 -06:00
|
|
|
luaL_checktype(state, 1, LUA_TUSERDATA);
|
2012-04-15 09:09:25 -06:00
|
|
|
luaL_checkstack(state, lua_gettop(state)+2, "stack overflow in event dispatch");
|
|
|
|
|
2012-08-23 09:27:12 -06:00
|
|
|
auto obj = (EventObject *)lua_touserdata(state, 1);
|
|
|
|
if (obj->owner)
|
|
|
|
obj->owner->on_invoked(state, lua_gettop(state)-1, false);
|
|
|
|
|
|
|
|
lua_getuservalue(state, 1);
|
|
|
|
lua_replace(state, 1);
|
2012-04-15 09:09:25 -06:00
|
|
|
dfhack_event_invoke(state, 0, false);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-08-23 09:27:12 -06:00
|
|
|
void DFHack::Lua::Event::Invoke(color_ostream &out, lua_State *state, void *key, int num_args)
|
2012-04-15 09:09:25 -06:00
|
|
|
{
|
|
|
|
AssertCoreSuspend(state);
|
|
|
|
|
|
|
|
int base = lua_gettop(state) - num_args;
|
|
|
|
|
|
|
|
if (!lua_checkstack(state, num_args+4))
|
|
|
|
{
|
|
|
|
out.printerr("Stack overflow in Lua::InvokeEvent");
|
|
|
|
lua_settop(state, base);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, key);
|
|
|
|
|
2012-08-23 09:27:12 -06:00
|
|
|
if (!lua_isuserdata(state, -1))
|
2012-04-15 09:09:25 -06:00
|
|
|
{
|
|
|
|
if (!lua_isnil(state, -1))
|
|
|
|
out.printerr("Invalid event object in Lua::InvokeEvent");
|
|
|
|
lua_settop(state, base);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-08-23 09:27:12 -06:00
|
|
|
auto obj = (EventObject *)lua_touserdata(state, -1);
|
2012-04-15 09:09:25 -06:00
|
|
|
lua_insert(state, base+1);
|
|
|
|
|
2012-08-23 09:27:12 -06:00
|
|
|
if (obj->owner)
|
|
|
|
obj->owner->on_invoked(state, num_args, true);
|
|
|
|
|
|
|
|
lua_getuservalue(state, base+1);
|
|
|
|
lua_replace(state, base+1);
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
color_ostream *cur_out = Lua::GetOutput(state);
|
|
|
|
set_dfhack_output(state, &out);
|
|
|
|
dfhack_event_invoke(state, base, true);
|
|
|
|
set_dfhack_output(state, cur_out);
|
|
|
|
}
|
|
|
|
|
2012-08-23 09:27:12 -06:00
|
|
|
void DFHack::Lua::Event::Make(lua_State *state, void *key, Owner *owner)
|
2012-04-15 09:09:25 -06:00
|
|
|
{
|
|
|
|
lua_rawgetp(state, LUA_REGISTRYINDEX, key);
|
|
|
|
|
|
|
|
if (lua_isnil(state, -1))
|
|
|
|
{
|
|
|
|
lua_pop(state, 1);
|
2012-08-23 09:27:12 -06:00
|
|
|
New(state, owner);
|
2012-04-15 09:09:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
lua_dup(state);
|
|
|
|
lua_rawsetp(state, LUA_REGISTRYINDEX, key);
|
|
|
|
}
|
|
|
|
|
|
|
|
void DFHack::Lua::Notification::invoke(color_ostream &out, int nargs)
|
|
|
|
{
|
|
|
|
assert(state);
|
2012-08-23 09:27:12 -06:00
|
|
|
Event::Invoke(out, state, key, nargs);
|
2012-04-15 09:09:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
void DFHack::Lua::Notification::bind(lua_State *state, void *key)
|
|
|
|
{
|
|
|
|
this->state = state;
|
|
|
|
this->key = key;
|
|
|
|
}
|
|
|
|
|
|
|
|
void DFHack::Lua::Notification::bind(lua_State *state, const char *name)
|
|
|
|
{
|
2012-08-23 09:27:12 -06:00
|
|
|
Event::Make(state, this);
|
2012-04-15 09:09:25 -06:00
|
|
|
|
|
|
|
if (handler)
|
|
|
|
{
|
|
|
|
PushFunctionWrapper(state, 0, name, handler);
|
2012-08-23 09:27:12 -06:00
|
|
|
Event::SetPrivateCallback(state, -2);
|
2012-04-15 09:09:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
this->state = state;
|
|
|
|
this->key = this;
|
|
|
|
}
|
|
|
|
|
2012-04-06 01:21:28 -06:00
|
|
|
/************************
|
|
|
|
* Main Open function *
|
|
|
|
************************/
|
|
|
|
|
2012-04-06 08:00:54 -06:00
|
|
|
void OpenDFHackApi(lua_State *state);
|
|
|
|
|
2012-12-21 03:00:50 -07:00
|
|
|
namespace DFHack { namespace Lua { namespace Core {
|
2021-03-11 00:31:30 -07:00
|
|
|
static void InitCoreContext(color_ostream &);
|
2012-12-21 03:00:50 -07:00
|
|
|
}}}
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
|
|
|
|
{
|
|
|
|
if (!state)
|
|
|
|
state = luaL_newstate();
|
|
|
|
|
2015-05-24 17:06:01 -06:00
|
|
|
interrupt_init(state);
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
luaL_openlibs(state);
|
|
|
|
AttachDFGlobals(state);
|
|
|
|
|
2012-04-16 08:05:42 -06:00
|
|
|
// Table of query coroutines
|
|
|
|
lua_newtable(state);
|
|
|
|
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_QUERY_COROTABLE_TOKEN);
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
// Replace the print function of the standard library
|
|
|
|
lua_pushcfunction(state, lua_dfhack_println);
|
|
|
|
lua_setglobal(state, "print");
|
|
|
|
|
2012-04-21 10:15:57 -06:00
|
|
|
lua_getglobal(state, "require");
|
|
|
|
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_REQUIRE_TOKEN);
|
|
|
|
lua_getglobal(state, "tostring");
|
|
|
|
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_TOSTRING_TOKEN);
|
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
// Create the dfhack global
|
|
|
|
lua_newtable(state);
|
|
|
|
|
2012-04-21 10:15:57 -06:00
|
|
|
lua_dup(state);
|
|
|
|
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN);
|
|
|
|
|
|
|
|
lua_rawgeti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
|
|
|
|
lua_dup(state);
|
|
|
|
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_BASE_G_TOKEN);
|
|
|
|
lua_setfield(state, -2, "BASE_G");
|
|
|
|
|
2015-04-02 14:37:58 -06:00
|
|
|
lua_pushstring(state, Version::dfhack_version());
|
2012-09-05 09:45:45 -06:00
|
|
|
lua_setfield(state, -2, "VERSION");
|
2015-04-02 14:37:58 -06:00
|
|
|
lua_pushstring(state, Version::df_version());
|
|
|
|
lua_setfield(state, -2, "DF_VERSION");
|
|
|
|
lua_pushstring(state, Version::dfhack_release());
|
|
|
|
lua_setfield(state, -2, "RELEASE");
|
2012-09-05 09:45:45 -06:00
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
lua_pushboolean(state, IsCoreContext(state));
|
|
|
|
lua_setfield(state, -2, "is_core_context");
|
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
// Create the metatable for exceptions
|
2012-03-31 05:40:54 -06:00
|
|
|
lua_newtable(state);
|
2012-04-04 03:34:07 -06:00
|
|
|
lua_pushcfunction(state, dfhack_exception_tostring);
|
|
|
|
lua_setfield(state, -2, "__tostring");
|
2012-06-22 06:36:50 -06:00
|
|
|
lua_pushcfunction(state, dfhack_exception_tostring);
|
|
|
|
lua_setfield(state, -2, "tostring");
|
2012-04-04 03:34:07 -06:00
|
|
|
lua_dup(state);
|
|
|
|
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
|
|
|
|
lua_setfield(state, -2, "exception");
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
lua_newtable(state);
|
|
|
|
lua_pushcfunction(state, dfhack_event_call);
|
|
|
|
lua_setfield(state, -2, "__call");
|
2012-08-23 09:27:12 -06:00
|
|
|
lua_pushcfunction(state, dfhack_event_len);
|
|
|
|
lua_setfield(state, -2, "__len");
|
|
|
|
lua_pushcfunction(state, dfhack_event_tostring);
|
|
|
|
lua_setfield(state, -2, "__tostring");
|
|
|
|
lua_pushcfunction(state, dfhack_event_index);
|
|
|
|
lua_setfield(state, -2, "__index");
|
|
|
|
lua_pushcfunction(state, dfhack_event_newindex);
|
|
|
|
lua_setfield(state, -2, "__newindex");
|
|
|
|
lua_pushcfunction(state, dfhack_event_pairs);
|
|
|
|
lua_setfield(state, -2, "__pairs");
|
2012-04-15 09:09:25 -06:00
|
|
|
lua_dup(state);
|
|
|
|
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EVENT_META_TOKEN);
|
2012-08-23 09:27:12 -06:00
|
|
|
|
|
|
|
lua_newtable(state);
|
|
|
|
lua_pushcfunction(state, dfhack_event_new);
|
|
|
|
lua_setfield(state, -2, "new");
|
|
|
|
lua_dup(state);
|
|
|
|
lua_setfield(state, -3, "__metatable");
|
|
|
|
lua_setfield(state, -3, "event");
|
|
|
|
lua_pop(state, 1);
|
2012-04-15 09:09:25 -06:00
|
|
|
|
2012-04-04 03:34:07 -06:00
|
|
|
// Initialize the dfhack global
|
2012-03-31 05:40:54 -06:00
|
|
|
luaL_setfuncs(state, dfhack_funcs, 0);
|
2012-04-01 06:43:40 -06:00
|
|
|
|
2012-04-06 08:00:54 -06:00
|
|
|
OpenDFHackApi(state);
|
2012-04-05 01:59:39 -06:00
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
lua_setglobal(state, "dfhack");
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
// stash the loaded module table into our own registry key
|
|
|
|
lua_getglobal(state, "package");
|
|
|
|
assert(lua_istable(state, -1));
|
|
|
|
lua_getfield(state, -1, "loaded");
|
|
|
|
assert(lua_istable(state, -1));
|
|
|
|
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_LOADED_TOKEN);
|
|
|
|
lua_pop(state, 1);
|
|
|
|
|
2012-04-16 04:45:04 -06:00
|
|
|
// replace some coroutine functions
|
|
|
|
lua_getglobal(state, "coroutine");
|
|
|
|
luaL_setfuncs(state, dfhack_coro_funcs, 0);
|
|
|
|
lua_pop(state, 1);
|
|
|
|
|
2021-02-27 22:38:59 -07:00
|
|
|
// replace some os functions
|
|
|
|
lua_getglobal(state, "os");
|
|
|
|
luaL_setfuncs(state, dfhack_os_funcs, 0);
|
|
|
|
lua_pop(state, 1);
|
|
|
|
|
2012-04-21 10:15:57 -06:00
|
|
|
// split the global environment
|
|
|
|
lua_newtable(state);
|
|
|
|
lua_newtable(state);
|
|
|
|
lua_rawgeti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
|
|
|
|
lua_setfield(state, -2, "__index");
|
|
|
|
lua_setmetatable(state, -2);
|
|
|
|
lua_dup(state);
|
|
|
|
lua_setglobal(state, "_G");
|
|
|
|
lua_dup(state);
|
|
|
|
lua_rawseti(state, LUA_REGISTRYINDEX, LUA_RIDX_GLOBALS);
|
|
|
|
|
2012-12-21 03:00:50 -07:00
|
|
|
// Init core-context specific stuff before loading dfhack.lua
|
|
|
|
if (IsCoreContext(state))
|
2021-03-11 00:31:30 -07:00
|
|
|
Lua::Core::InitCoreContext(out);
|
2021-02-27 22:38:59 -07:00
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
// load dfhack.lua
|
2015-10-17 19:18:04 -06:00
|
|
|
if (!Require(out, state, "dfhack"))
|
|
|
|
{
|
|
|
|
out.printerr("Could not load dfhack.lua\n");
|
|
|
|
return NULL;
|
|
|
|
}
|
2012-03-31 05:40:54 -06:00
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
lua_settop(state, 0);
|
|
|
|
if (!lua_checkstack(state, 64))
|
|
|
|
out.printerr("Could not extend initial lua stack size to 64 items.\n");
|
|
|
|
|
2012-03-31 05:40:54 -06:00
|
|
|
return state;
|
|
|
|
}
|
|
|
|
|
2012-05-04 10:59:06 -06:00
|
|
|
static int next_timeout_id = 0;
|
|
|
|
static int frame_idx = 0;
|
|
|
|
static std::multimap<int,int> frame_timers;
|
|
|
|
static std::multimap<int,int> tick_timers;
|
|
|
|
|
|
|
|
int DFHACK_TIMEOUTS_TOKEN = 0;
|
|
|
|
|
|
|
|
static const char *const timeout_modes[] = {
|
|
|
|
"frames", "ticks", "days", "months", "years", NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
int dfhack_timeout(lua_State *L)
|
|
|
|
{
|
|
|
|
using df::global::world;
|
|
|
|
using df::global::enabler;
|
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
// Parse arguments
|
2012-05-04 10:59:06 -06:00
|
|
|
lua_Number time = luaL_checknumber(L, 1);
|
|
|
|
int mode = luaL_checkoption(L, 2, NULL, timeout_modes);
|
|
|
|
luaL_checktype(L, 3, LUA_TFUNCTION);
|
|
|
|
lua_settop(L, 3);
|
|
|
|
|
|
|
|
if (mode > 0 && !Core::getInstance().isWorldLoaded())
|
|
|
|
{
|
|
|
|
lua_pushnil(L);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
// Compute timeout value
|
2012-05-04 10:59:06 -06:00
|
|
|
switch (mode)
|
|
|
|
{
|
|
|
|
case 2:
|
|
|
|
time *= 1200;
|
|
|
|
break;
|
|
|
|
case 3:
|
|
|
|
time *= 33600;
|
|
|
|
break;
|
|
|
|
case 4:
|
|
|
|
time *= 403200;
|
|
|
|
break;
|
|
|
|
default:;
|
|
|
|
}
|
|
|
|
|
|
|
|
int delta = time;
|
|
|
|
|
|
|
|
if (delta <= 0)
|
|
|
|
luaL_error(L, "Invalid timeout: %d", delta);
|
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
// Queue the timeout
|
|
|
|
int id = next_timeout_id++;
|
2012-05-04 10:59:06 -06:00
|
|
|
if (mode)
|
|
|
|
tick_timers.insert(std::pair<int,int>(world->frame_counter+delta, id));
|
|
|
|
else
|
|
|
|
frame_timers.insert(std::pair<int,int>(frame_idx+delta, id));
|
|
|
|
|
|
|
|
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
|
|
|
|
lua_swap(L);
|
|
|
|
lua_rawseti(L, -2, id);
|
|
|
|
|
|
|
|
lua_pushinteger(L, id);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
int dfhack_timeout_active(lua_State *L)
|
|
|
|
{
|
|
|
|
int id = luaL_optint(L, 1, -1);
|
|
|
|
bool set_cb = (lua_gettop(L) >= 2);
|
|
|
|
lua_settop(L, 2);
|
|
|
|
if (!lua_isnil(L, 2))
|
|
|
|
luaL_checktype(L, 2, LUA_TFUNCTION);
|
|
|
|
|
|
|
|
if (id < 0)
|
|
|
|
{
|
|
|
|
lua_pushnil(L);
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
lua_rawgetp(L, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
|
|
|
|
lua_rawgeti(L, 3, id);
|
|
|
|
if (set_cb && !lua_isnil(L, -1))
|
|
|
|
{
|
|
|
|
lua_pushvalue(L, 2);
|
|
|
|
lua_rawseti(L, 3, id);
|
|
|
|
}
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cancel_timers(std::multimap<int,int> &timers)
|
2012-05-04 10:59:06 -06:00
|
|
|
{
|
|
|
|
using Lua::Core::State;
|
|
|
|
|
|
|
|
Lua::StackUnwinder frame(State);
|
|
|
|
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
|
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
for (auto it = timers.begin(); it != timers.end(); ++it)
|
2012-05-04 10:59:06 -06:00
|
|
|
{
|
|
|
|
lua_pushnil(State);
|
|
|
|
lua_rawseti(State, frame[1], it->second);
|
|
|
|
}
|
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
timers.clear();
|
2012-05-04 10:59:06 -06:00
|
|
|
}
|
|
|
|
|
2012-04-17 01:45:09 -06:00
|
|
|
void DFHack::Lua::Core::onStateChange(color_ostream &out, int code) {
|
|
|
|
if (!State) return;
|
|
|
|
|
2012-05-04 10:59:06 -06:00
|
|
|
switch (code)
|
|
|
|
{
|
|
|
|
case SC_MAP_UNLOADED:
|
|
|
|
case SC_WORLD_UNLOADED:
|
2012-05-16 07:06:08 -06:00
|
|
|
cancel_timers(tick_timers);
|
2012-05-04 10:59:06 -06:00
|
|
|
break;
|
|
|
|
|
|
|
|
default:;
|
|
|
|
}
|
|
|
|
|
2012-04-17 01:45:09 -06:00
|
|
|
Lua::Push(State, code);
|
2012-08-23 09:27:12 -06:00
|
|
|
Lua::Event::Invoke(out, State, (void*)onStateChange, 1);
|
2012-04-17 01:45:09 -06:00
|
|
|
}
|
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
static void run_timers(color_ostream &out, lua_State *L,
|
|
|
|
std::multimap<int,int> &timers, int table, int bound)
|
2012-05-04 10:59:06 -06:00
|
|
|
{
|
2012-05-16 07:06:08 -06:00
|
|
|
while (!timers.empty() && timers.begin()->first <= bound)
|
2012-05-04 10:59:06 -06:00
|
|
|
{
|
2012-05-16 07:06:08 -06:00
|
|
|
int id = timers.begin()->second;
|
|
|
|
timers.erase(timers.begin());
|
2012-05-04 10:59:06 -06:00
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
lua_rawgeti(L, table, id);
|
|
|
|
|
|
|
|
if (lua_isnil(L, -1))
|
|
|
|
lua_pop(L, 1);
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lua_pushnil(L);
|
|
|
|
lua_rawseti(L, table, id);
|
2012-05-04 10:59:06 -06:00
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
Lua::SafeCall(out, L, 0, 0);
|
|
|
|
}
|
2012-05-04 10:59:06 -06:00
|
|
|
}
|
2012-05-16 07:06:08 -06:00
|
|
|
}
|
2012-05-04 10:59:06 -06:00
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
void DFHack::Lua::Core::onUpdate(color_ostream &out)
|
|
|
|
{
|
|
|
|
using df::global::world;
|
2012-05-04 10:59:06 -06:00
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
if (frame_timers.empty() && tick_timers.empty())
|
|
|
|
return;
|
2012-05-04 10:59:06 -06:00
|
|
|
|
2012-05-16 07:06:08 -06:00
|
|
|
Lua::StackUnwinder frame(State);
|
|
|
|
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
|
|
|
|
|
|
|
|
run_timers(out, State, frame_timers, frame[1], ++frame_idx);
|
2012-09-22 04:52:08 -06:00
|
|
|
|
|
|
|
if (world)
|
|
|
|
run_timers(out, State, tick_timers, frame[1], world->frame_counter);
|
2012-05-04 10:59:06 -06:00
|
|
|
}
|
|
|
|
|
2015-10-17 19:18:04 -06:00
|
|
|
bool DFHack::Lua::Core::Init(color_ostream &out)
|
2012-04-15 09:09:25 -06:00
|
|
|
{
|
2015-10-17 19:18:04 -06:00
|
|
|
if (State) {
|
|
|
|
out.printerr("state already exists\n");
|
|
|
|
return false;
|
|
|
|
}
|
2012-04-15 09:09:25 -06:00
|
|
|
|
|
|
|
State = luaL_newstate();
|
2012-04-17 01:45:09 -06:00
|
|
|
|
2012-12-21 03:00:50 -07:00
|
|
|
// Calls InitCoreContext after checking IsCoreContext
|
2015-10-17 19:18:04 -06:00
|
|
|
return (Lua::Open(out, State) != NULL);
|
2012-12-21 03:00:50 -07:00
|
|
|
}
|
2012-04-17 01:45:09 -06:00
|
|
|
|
2021-03-11 00:31:30 -07:00
|
|
|
static void Lua::Core::InitCoreContext(color_ostream &out)
|
2012-12-21 03:00:50 -07:00
|
|
|
{
|
2012-05-04 10:59:06 -06:00
|
|
|
lua_newtable(State);
|
|
|
|
lua_rawsetp(State, LUA_REGISTRYINDEX, &DFHACK_TIMEOUTS_TOKEN);
|
|
|
|
|
2012-04-17 01:45:09 -06:00
|
|
|
// Register events
|
2012-04-21 10:15:57 -06:00
|
|
|
lua_rawgetp(State, LUA_REGISTRYINDEX, &DFHACK_DFHACK_TOKEN);
|
2012-04-17 01:45:09 -06:00
|
|
|
|
2012-08-23 09:27:12 -06:00
|
|
|
Event::Make(State, (void*)onStateChange);
|
2012-04-17 01:45:09 -06:00
|
|
|
lua_setfield(State, -2, "onStateChange");
|
|
|
|
|
2012-05-04 10:59:06 -06:00
|
|
|
lua_pushcfunction(State, dfhack_timeout);
|
|
|
|
lua_setfield(State, -2, "timeout");
|
2012-05-16 07:06:08 -06:00
|
|
|
lua_pushcfunction(State, dfhack_timeout_active);
|
|
|
|
lua_setfield(State, -2, "timeout_active");
|
2012-05-04 10:59:06 -06:00
|
|
|
|
2012-04-17 01:45:09 -06:00
|
|
|
lua_pop(State, 1);
|
2021-03-11 00:31:30 -07:00
|
|
|
|
|
|
|
if (getenv("DFHACK_ENABLE_LUACOV"))
|
|
|
|
{
|
|
|
|
// reads config from .luacov or uses defaults if file doesn't exist.
|
|
|
|
// note that luacov overrides the debug hook installed by
|
|
|
|
// interrupt_init() above.
|
|
|
|
if (Lua::PushModulePublic(out, State, "luacov.runner", "init") &&
|
|
|
|
Lua::SafeCall(out, State, 0, 0))
|
|
|
|
{
|
|
|
|
out.print("Initialized luacov coverage monitoring\n");
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
out.printerr("Failed to initialize luacov coverage monitoring\n");
|
|
|
|
// non-fatal error
|
|
|
|
}
|
|
|
|
}
|
2012-04-15 09:09:25 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
void DFHack::Lua::Core::Reset(color_ostream &out, const char *where)
|
|
|
|
{
|
2020-02-03 19:21:10 -07:00
|
|
|
// This can happen if DFHack fails to initialize.
|
|
|
|
if (!State)
|
|
|
|
return;
|
|
|
|
|
2012-04-15 09:09:25 -06:00
|
|
|
int top = lua_gettop(State);
|
|
|
|
|
|
|
|
if (top != 0)
|
|
|
|
{
|
|
|
|
out.printerr("Common lua context stack top left at %d after %s.\n", top, where);
|
|
|
|
lua_settop(State, 0);
|
|
|
|
}
|
|
|
|
}
|