diff --git a/LUA_API.rst b/LUA_API.rst index 5136bba57..e8c413fe7 100644 --- a/LUA_API.rst +++ b/LUA_API.rst @@ -426,13 +426,17 @@ not destroy any objects allocated in this way, so the user should be prepared to catch the error and do the necessary cleanup. -================ -DFHack utilities -================ +========== +DFHack API +========== DFHack utility functions are placed in the ``dfhack`` global tree. -Currently it defines the following features: +Native utilities +================ + +Input & Output +-------------- * ``dfhack.print(args...)`` @@ -474,23 +478,9 @@ Currently it defines the following features: If the interactive console is not accessible, returns *nil, error*. -* ``dfhack.pcall(f[,args...])`` - - Invokes f via xpcall, using an error function that attaches - a stack trace to the error. The same function is used by SafeCall - in C++, and dfhack.safecall. - - The returned error is a table with separate ``message`` and - ``stacktrace`` string fields; it implements ``__tostring``. - -* ``safecall(f[,args...])``, ``dfhack.safecall(f[,args...])`` - - Just like pcall, but also prints the error using printerr before - returning. Intended as a convenience function. - -* ``dfhack.saferesume(coroutine[,args...])`` - Compares to coroutine.resume like dfhack.safecall vs pcall. +Miscellaneous +------------- * ``dfhack.run_script(name[,args...])`` @@ -511,6 +501,36 @@ 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. + +Exception handling +------------------ + +* ``dfhack.error(msg[,level[,verbose]])`` + + Throws a dfhack exception object with location and stack trace. + The verbose parameter controls whether the trace is printed by default. + +* ``qerror(msg[,level])`` + + Calls ``dfhack.error()`` with ``verbose`` being *false*. Intended to + be used for user-caused errors in scripts, where stack traces are not + desirable. + +* ``dfhack.pcall(f[,args...])`` + + Invokes f via xpcall, using an error function that attaches + a stack trace to the error. The same function is used by SafeCall + in C++, and dfhack.safecall. + +* ``safecall(f[,args...])``, ``dfhack.safecall(f[,args...])`` + + Just like pcall, but also prints the error using printerr before + returning. Intended as a convenience function. + +* ``dfhack.saferesume(coroutine[,args...])`` + + Compares to coroutine.resume like dfhack.safecall vs pcall. + * ``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 @@ -535,9 +555,33 @@ Currently it defines the following features: Calls ``fn(obj,args...)``, then finalizes with ``obj:delete()``. +* ``dfhack.exception`` + + Metatable of error objects used by dfhack. The objects have the + following properties: + + ``err.where`` + The location prefix string, or *nil*. + ``err.message`` + The base message string. + ``err.stacktrace`` + The stack trace string, or *nil*. + ``err.cause`` + A different exception object, or *nil*. + ``err.thread`` + The coroutine that has thrown the exception. + ``err.verbose`` + Boolean, or *nil*; specifies if where and stacktrace should be printed. + ``tostring(err)``, or ``err:tostring([verbose])`` + Converts the exception to string. + +* ``dfhack.exception.verbose`` + + The default value of the ``verbose`` argument of ``err:tostring()``. + Persistent configuration storage -================================ +-------------------------------- This api is intended for storing configuration options in the world itself. It probably should be restricted to data that is world-dependent. @@ -579,7 +623,7 @@ functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead. Material info lookup -==================== +-------------------- A material info record has fields: diff --git a/Lua API.html b/Lua API.html index 1c4dc4059..47cf08ab6 100644 --- a/Lua API.html +++ b/Lua API.html @@ -333,30 +333,36 @@ ul.auto-toc {
DFHack utility functions are placed in the dfhack global tree.
-Currently it defines the following features:
+dfhack.print(args...)
Output tab-separated args as standard lua print would do, @@ -753,20 +762,11 @@ this, forcing the function to block on input with lock held.
string, global environment and command-line history file.If the interactive console is not accessible, returns nil, error.
dfhack.pcall(f[,args...])
-Invokes f via xpcall, using an error function that attaches -a stack trace to the error. The same function is used by SafeCall -in C++, and dfhack.safecall.
-The returned error is a table with separate message and -stacktrace string fields; it implements __tostring.
-safecall(f[,args...]), dfhack.safecall(f[,args...])
-Just like pcall, but also prints the error using printerr before -returning. Intended as a convenience function.
-dfhack.saferesume(coroutine[,args...])
-Compares to coroutine.resume like dfhack.safecall vs pcall.
-dfhack.run_script(name[,args...])
Run a lua script in hack/scripts/, as if it was started from dfhack command-line. The name argument should be the name stem, as would be used on the command line. @@ -782,6 +782,32 @@ 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.error(msg[,level[,verbose]])
+Throws a dfhack exception object with location and stack trace. +The verbose parameter controls whether the trace is printed by default.
+qerror(msg[,level])
+Calls dfhack.error() with verbose being false. Intended to +be used for user-caused errors in scripts, where stack traces are not +desirable.
+dfhack.pcall(f[,args...])
+Invokes f via xpcall, using an error function that attaches +a stack trace to the error. The same function is used by SafeCall +in C++, and dfhack.safecall.
+safecall(f[,args...]), dfhack.safecall(f[,args...])
+Just like pcall, but also prints the error using printerr before +returning. Intended as a convenience function.
+dfhack.saferesume(coroutine[,args...])
+Compares to coroutine.resume like dfhack.safecall vs pcall.
+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
@@ -801,9 +827,40 @@ Implemented using call_with_final
dfhack.with_temp_object(obj,fn[,args...]) Calls fn(obj,args...), then finalizes with obj:delete(). dfhack.exception Metatable of error objects used by dfhack. The objects have the
+following properties: The location prefix string, or nil. The base message string. The stack trace string, or nil. A different exception object, or nil. The coroutine that has thrown the exception. Boolean, or nil; specifies if where and stacktrace should be printed. Converts the exception to string. dfhack.exception.verbose The default value of the verbose argument of err:tostring().
+
+
This api is intended for storing configuration options in the world itself. It probably should be restricted to data that is world-dependent.
Entries are identified by a string key, but it is also possible to manage @@ -838,7 +895,7 @@ functions can just copy values in memory without doing any actual I/O. However, currently every entry has a 180+-byte dead-weight overhead.
A material info record has fields:
type, index, material
@@ -881,8 +938,9 @@ Accept dfhack_material_category auto-assign table.Thin wrappers around C++ functions, similar to the ones for virtual methods. One notable difference is that these explicit wrappers allow argument count adjustment according to the usual lua rules, so trailing false/nil arguments @@ -911,7 +969,7 @@ can be omitted.
dfhack.gui.getCurViewscreen()
Returns the viewscreen that is current in the core.
@@ -947,7 +1005,7 @@ The is_bright boolean actually seems to invert the brightness.dfhack.job.cloneJobStruct(job)
Creates a deep copy of the given job.
@@ -984,7 +1042,7 @@ a lua list containing them.dfhack.units.getPosition(unit)
Returns true x,y,z of the unit, or nil if invalid; may be not equal to unit.pos if caged.
@@ -1038,7 +1096,7 @@ or raws. The ignore_noble boolean disables thedfhack.items.getPosition(item)
Returns true x,y,z of the item, or nil if invalid; may be not equal to item.pos if in inventory.
@@ -1081,7 +1139,7 @@ Returns false in case of error.dfhack.maps.getSize()
Returns map size in blocks: x, y, z
@@ -1122,7 +1180,7 @@ burrows, or the presence of invaders.dfhack.burrows.findByName(name)
Returns the burrow pointer or nil.
@@ -1157,7 +1215,7 @@ burrows, or the presence of invaders.dfhack.buildings.getSize(building)
Returns width, height, centerx, centery.
@@ -1297,7 +1355,7 @@ can be determined this way, constructBuildingdfhack.constructions.designateNew(pos,type,item_type,mat_index)
Designates a new construction at given position. If there already is @@ -1313,7 +1371,7 @@ Returns true, was_only_planned if removed; or false if none fo
These functions are intended for the use by dfhack developers, and are only documented here for completeness:
While plugins can create any number of interpreter instances, there is one special context managed by dfhack core. It is the only context that can receive events from DF and plugins.
@@ -1387,7 +1445,7 @@ Using timeout_active(id,nil) cancels the timerAn event is just a lua table with a predefined metatable that contains a __call metamethod. When it is invoked, it loops through the table with next and calls all contained values. @@ -1413,14 +1471,14 @@ order using dfhack.safecall.
DFHack plugins may export native functions and events to lua contexts. They are automatically imported by mkmodule('plugins.<name>'); this means that a lua module file is still necessary for require to read.
The following plugins have lua support.
diff --git a/library/LuaTools.cpp b/library/LuaTools.cpp index 48244dedf..28571a0f7 100644 --- a/library/LuaTools.cpp +++ b/library/LuaTools.cpp @@ -426,10 +426,12 @@ static bool convert_to_exception(lua_State *L, int slevel, lua_State *thread = N // Create a new exception for this thread lua_newtable(L); - luaL_where(L, 1); + luaL_where(L, slevel); + lua_setfield(L, -2, "where"); lua_pushstring(L, "coroutine resume failed"); - lua_concat(L, 2); lua_setfield(L, -2, "message"); + lua_getfield(L, -2, "verbose"); + lua_setfield(L, -2, "verbose"); lua_swap(L); lua_setfield(L, -2, "cause"); } @@ -483,12 +485,57 @@ static int dfhack_onerror(lua_State *L) return 1; } +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); +} + static int dfhack_exception_tostring(lua_State *L) { luaL_checktype(L, 1, LUA_TTABLE); + 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)); int base = lua_gettop(L); + if (verbose || lua_isnil(L, 3)) + { + lua_getfield(L, 1, "where"); + if (!lua_isstring(L, -1)) + lua_pop(L, 1); + } + lua_getfield(L, 1, "message"); if (!lua_isstring(L, -1)) { @@ -496,15 +543,26 @@ static int dfhack_exception_tostring(lua_State *L) lua_pushstring(L, "(error message is not a string)"); } - lua_pushstring(L, "\n"); - lua_getfield(L, 1, "stacktrace"); - if (!lua_isstring(L, -1)) - lua_pop(L, 2); + if (verbose) + { + lua_pushstring(L, "\n"); + lua_getfield(L, 1, "stacktrace"); + 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 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); + } else error_tostring(L); @@ -655,7 +713,12 @@ static int dfhack_coauxwrap (lua_State *L) { if (Lua::IsSuccess(r)) return lua_gettop(L); else + { + if (lua_checkstack(L, LUA_MINSTACK)) + convert_to_exception(L, 1); + return lua_error(L); + } } static int dfhack_cowrap (lua_State *L) { @@ -1162,6 +1225,7 @@ static const luaL_Reg dfhack_funcs[] = { { "safecall", dfhack_safecall }, { "saferesume", dfhack_saferesume }, { "onerror", dfhack_onerror }, + { "error", dfhack_error }, { "call_with_finalizer", dfhack_call_with_finalizer }, { "with_suspend", lua_dfhack_with_suspend }, { "open_plugin", dfhack_open_plugin }, @@ -1362,6 +1426,8 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state) lua_newtable(state); lua_pushcfunction(state, dfhack_exception_tostring); lua_setfield(state, -2, "__tostring"); + lua_pushcfunction(state, dfhack_exception_tostring); + lua_setfield(state, -2, "tostring"); lua_dup(state); lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN); lua_setfield(state, -2, "exception"); diff --git a/library/lua/dfhack.lua b/library/lua/dfhack.lua index 4cdb4c950..d200a6c5c 100644 --- a/library/lua/dfhack.lua +++ b/library/lua/dfhack.lua @@ -49,6 +49,10 @@ function dfhack.pcall(f, ...) return xpcall(f, dfhack.onerror, ...) end +function qerror(msg, level) + dfhack.error(msg, (level or 1) + 1, false) +end + function dfhack.with_finalize(...) return dfhack.call_with_finalizer(0,true,...) end diff --git a/library/lua/memscan.lua b/library/lua/memscan.lua index 92a3e3e80..970f821c2 100644 --- a/library/lua/memscan.lua +++ b/library/lua/memscan.lua @@ -235,7 +235,7 @@ function found_offset(name,val) if not val then print('Could not find offset '..name) if not cval and not utils.prompt_yes_no('Continue with the script?') then - error('User quit') + qerror('User quit') end return end diff --git a/library/lua/utils.lua b/library/lua/utils.lua index 93ee840c4..f303091d6 100644 --- a/library/lua/utils.lua +++ b/library/lua/utils.lua @@ -379,7 +379,7 @@ function prompt_yes_no(msg,default) elseif string.match(rv,'^[Nn]') then return false elseif rv == 'abort' then - error('User abort in utils.prompt_yes_no()') + qerror('User abort in utils.prompt_yes_no()') elseif rv == '' and default ~= nil then return default end diff --git a/scripts/devel/find-offsets.lua b/scripts/devel/find-offsets.lua index bddd29dfe..6fc127351 100644 --- a/scripts/devel/find-offsets.lua +++ b/scripts/devel/find-offsets.lua @@ -43,12 +43,12 @@ end local data = ms.get_data_segment() if not data then - error('Could not find data segment') + qerror('Could not find data segment') end print('\nData section: '..tostring(data)) if data.size < 5000000 then - error('Data segment too short.') + qerror('Data segment too short.') end local searcher = ms.DiffSearcher.new(data) @@ -103,7 +103,7 @@ local function exec_finder(finder, names) if not dfhack.safecall(finder) then if not utils.prompt_yes_no('Proceed with the rest of the script?') then searcher:reset() - error('Quit') + qerror('Quit') end end else diff --git a/scripts/fix/item-occupancy.lua b/scripts/fix/item-occupancy.lua index b5466b7a8..09c6b3030 100644 --- a/scripts/fix/item-occupancy.lua +++ b/scripts/fix/item-occupancy.lua @@ -116,8 +116,7 @@ if opt then if opt == '--fix' then fix = true else - dfhack.printerr('Invalid option: '..opt) - return + qerror('Invalid option: '..opt) end end diff --git a/scripts/quicksave.lua b/scripts/quicksave.lua index c54cc730b..f4886b35b 100644 --- a/scripts/quicksave.lua +++ b/scripts/quicksave.lua @@ -1,8 +1,7 @@ -- Makes the game immediately save the state. if not dfhack.isMapLoaded() then - dfhack.printerr("World and map aren't loaded.") - return + qerror("World and map aren't loaded.") end local ui_main = df.global.ui.main