diff --git a/LUA_API.rst b/LUA_API.rst index 0ec837a2d..57a3fa16c 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -456,6 +456,31 @@ Currently it defines the following features: to group operations together in one big critical section. A plugin can choose to run all lua code inside a C++-side suspend lock. +* ``dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])`` + + Invokes ``fn`` with ``args``, and after it returns or throws an + error calls ``cleanup_fn`` with ``cleanup_args``. Any return values from + ``fn`` are propagated, and errors are re-thrown. + + The ``num_cleanup_args`` integer specifies the number of ``cleanup_args``, + and the ``always`` boolean specifies if cleanup should be called in any case, + or only in case of an error. + +* ``dfhack.with_finalize(cleanup_fn,fn[,args...])`` + + Calls ``fn`` with arguments, then finalizes with ``cleanup_fn``. + Implemented using ``call_with_finalizer(0,true,...)``. + +* ``dfhack.with_onerror(cleanup_fn,fn[,args...])`` + + Calls ``fn`` with arguments, then finalizes with ``cleanup_fn`` on any thrown error. + Implemented using ``call_with_finalizer(0,false,...)``. + +* ``dfhack.with_temp_object(obj,fn[,args...])`` + + Calls ``fn(obj,args...)``, then finalizes with ``obj:delete()``. + + Persistent configuration storage ================================ diff --git a/Lua API.html b/Lua API.html index 379432a78..19f912b72 100644 --- a/Lua API.html +++ b/Lua API.html @@ -724,6 +724,25 @@ the lock. It is safe to nest suspends.

to group operations together in one big critical section. A plugin can choose to run all lua code inside a C++-side suspend lock.

+
  • dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])

    +

    Invokes fn with args, and after it returns or throws an +error calls cleanup_fn with cleanup_args. Any return values from +fn are propagated, and errors are re-thrown.

    +

    The num_cleanup_args integer specifies the number of cleanup_args, +and the always boolean specifies if cleanup should be called in any case, +or only in case of an error.

    +
  • +
  • dfhack.with_finalize(cleanup_fn,fn[,args...])

    +

    Calls fn with arguments, then finalizes with cleanup_fn. +Implemented using call_with_finalizer(0,true,...).

    +
  • +
  • dfhack.with_onerror(cleanup_fn,fn[,args...])

    +

    Calls fn with arguments, then finalizes with cleanup_fn on any thrown error. +Implemented using call_with_finalizer(0,false,...).

    +
  • +
  • dfhack.with_temp_object(obj,fn[,args...])

    +

    Calls fn(obj,args...), then finalizes with obj:delete().

    +
  • Persistent configuration storage

    @@ -799,7 +818,8 @@ or obj.mat_type/o

    Returns the classification used for craft skills.

  • info:matches(obj)

    -

    Checks if the material matches job_material_category or job_item.

    +

    Checks if the material matches job_material_category or job_item. +Accept dfhack_material_category auto-assign table.

  • diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 1e7a83b30..471ffd550 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -226,10 +226,14 @@ static int lua_dfhack_lineedit(lua_State *S) static int DFHACK_EXCEPTION_META_TOKEN = 0; -static void error_tostring(lua_State *L) +static void error_tostring(lua_State *L, bool keep_old = false) { lua_getglobal(L, "tostring"); - lua_pushvalue(L, -2); + if (keep_old) + lua_pushvalue(L, -2); + else + lua_swap(L); + bool ok = lua_pcall(L, 1, 1, 0) == LUA_OK; const char *msg = lua_tostring(L, -1); @@ -248,8 +252,7 @@ static void error_tostring(lua_State *L) static void report_error(lua_State *L, color_ostream *out = NULL) { - lua_dup(L); - error_tostring(L); + error_tostring(L, true); const char *msg = lua_tostring(L, -1); assert(msg); @@ -290,7 +293,7 @@ static bool convert_to_exception(lua_State *L) lua_setfield(L, base, "message"); else { - error_tostring(L); + error_tostring(L, true); lua_setfield(L, base, "message"); lua_setfield(L, base, "object"); } @@ -346,24 +349,51 @@ static int dfhack_exception_tostring(lua_State *L) if (!lua_isstring(L, -1)) lua_pop(L, 2); + lua_pushstring(L, "\ncaused by:\n"); + lua_getfield(L, 1, "cause"); + if (lua_isnil(L, -1)) + lua_pop(L, 2); + else + error_tostring(L); + lua_concat(L, lua_gettop(L) - base); return 1; } -static int finish_dfhack_safecall (lua_State *L, bool success) +static void push_simple_error(lua_State *L, const char *str) { - if (!lua_checkstack(L, 2)) + lua_pushstring(L, str); + + if (lua_checkstack(L, 5)) + convert_to_exception(L); + + if (lua_checkstack(L, LUA_MINSTACK)) + { + luaL_traceback(L, L, NULL, 1); + lua_setfield(L, -2, "stacktrace"); + } +} + +static bool do_finish_pcall(lua_State *L, bool success, int base = 1, int space = 2) +{ + if (!lua_checkstack(L, space)) { - lua_settop(L, 0); /* create space for return values */ + lua_settop(L, base-1); /* create space for return values */ lua_pushboolean(L, 0); - lua_pushstring(L, "stack overflow in dfhack.safecall()"); - success = false; + push_simple_error(L, "stack overflow"); + return false; } else { lua_pushboolean(L, success); - lua_replace(L, 1); /* put first result in first slot */ + lua_replace(L, base); /* put first result in first slot */ + return true; } +} + +static int finish_dfhack_safecall (lua_State *L, bool success) +{ + success = do_finish_pcall(L, success); if (!success) report_error(L); @@ -599,32 +629,118 @@ static int lua_dfhack_interpreter(lua_State *state) return 1; } -static int lua_dfhack_with_suspend(lua_State *L) +static bool do_invoke_cleanup(lua_State *L, int nargs, int errorfun, bool success) { - int rv = lua_getctx(L, NULL); + bool ok = lua_pcall(L, nargs, 0, errorfun) == LUA_OK; - // Non-resume entry point: - if (rv == LUA_OK) + if (!ok) { - int nargs = lua_gettop(L); + // If finalization failed, attach the previous error + if (lua_istable(L, -1) && !success) + { + lua_swap(L); + lua_setfield(L, -2, "cause"); + } - luaL_checktype(L, 1, LUA_TFUNCTION); + success = false; + } + + return success; +} - Core::getInstance().Suspend(); +static int finish_dfhack_cleanup (lua_State *L, bool success) +{ + 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...] - lua_pushcfunction(L, dfhack_onerror); - lua_insert(L, 1); + 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); + } + } - rv = lua_pcallk(L, nargs-1, LUA_MULTRET, 1, 0, lua_dfhack_with_suspend); + success = do_invoke_cleanup(L, nargs, 3, success); } - // Return, resume, or error entry point: - lua_remove(L, 1); + if (!success) + lua_error(L); + + return numret; +} + +static int dfhack_cleanup_cont (lua_State *L) +{ + int status = lua_getctx(L, NULL); + return finish_dfhack_cleanup(L, (status == LUA_YIELD)); +} - Core::getInstance().Resume(); +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); - if (rv != LUA_OK && rv != LUA_YIELD) + // 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); lua_error(L); + } + + // Actually invoke + + // stack: [nargs] [always] [errorfun] [cleanup fun] [cleanup args...] |rvbase+1:| [fun] [args...] + int status = lua_pcallk(L, lua_gettop(L)-rvbase-1, LUA_MULTRET, 3, 0, dfhack_cleanup_cont); + return finish_dfhack_cleanup(L, (status == LUA_OK)); +} + +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); return lua_gettop(L); } @@ -639,6 +755,7 @@ static const luaL_Reg dfhack_funcs[] = { { "interpreter", lua_dfhack_interpreter }, { "safecall", lua_dfhack_safecall }, { "onerror", dfhack_onerror }, + { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, { NULL, NULL } }; diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index d662efed7..b37183cb6 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -29,6 +29,21 @@ function dfhack.pcall(f, ...) return xpcall(f, dfhack.onerror, ...) end +function dfhack.with_finalize(...) + return dfhack.call_with_finalizer(0,true,...) +end +function dfhack.with_onerror(...) + return dfhack.call_with_finalizer(0,false,...) +end + +local function call_delete(obj) + if obj then obj:delete() end +end + +function dfhack.with_temp_object(obj,fn,...) + return dfhack.call_with_finalizer(1,true,call_delete,obj,fn,obj,...) +end + -- Module loading function mkmodule(module,env)