Support controllable error presentation verbosity in lua code.

Use qerror to squash stack traces and location prefix.
develop
Alexander Gavrilov 2012-06-22 16:36:50 +04:00
parent 752da9ced5
commit 65e82f7c12
9 changed files with 259 additions and 89 deletions

@ -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 should be prepared to catch the error and do the necessary
cleanup. cleanup.
================ ==========
DFHack utilities DFHack API
================ ==========
DFHack utility functions are placed in the ``dfhack`` global tree. DFHack utility functions are placed in the ``dfhack`` global tree.
Currently it defines the following features: Native utilities
================
Input & Output
--------------
* ``dfhack.print(args...)`` * ``dfhack.print(args...)``
@ -474,23 +478,9 @@ Currently it defines the following features:
If the interactive console is not accessible, returns *nil, error*. 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...])`` * ``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 to group operations together in one big critical section. A plugin
can choose to run all lua code inside a C++-side suspend lock. 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...])`` * ``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 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()``. 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 Persistent configuration storage
================================ --------------------------------
This api is intended for storing configuration options in the world itself. This api is intended for storing configuration options in the world itself.
It probably should be restricted to data that is world-dependent. 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. However, currently every entry has a 180+-byte dead-weight overhead.
Material info lookup Material info lookup
==================== --------------------
A material info record has fields: A material info record has fields:

@ -333,30 +333,36 @@ ul.auto-toc {
<li><a class="reference internal" href="#recursive-table-assignment" id="id9">Recursive table assignment</a></li> <li><a class="reference internal" href="#recursive-table-assignment" id="id9">Recursive table assignment</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#dfhack-utilities" id="id10">DFHack utilities</a><ul> <li><a class="reference internal" href="#dfhack-api" id="id10">DFHack API</a><ul>
<li><a class="reference internal" href="#persistent-configuration-storage" id="id11">Persistent configuration storage</a></li> <li><a class="reference internal" href="#native-utilities" id="id11">Native utilities</a><ul>
<li><a class="reference internal" href="#material-info-lookup" id="id12">Material info lookup</a></li> <li><a class="reference internal" href="#input-output" id="id12">Input &amp; Output</a></li>
<li><a class="reference internal" href="#c-function-wrappers" id="id13">C++ function wrappers</a><ul> <li><a class="reference internal" href="#miscellaneous" id="id13">Miscellaneous</a></li>
<li><a class="reference internal" href="#gui-module" id="id14">Gui module</a></li> <li><a class="reference internal" href="#exception-handling" id="id14">Exception handling</a></li>
<li><a class="reference internal" href="#job-module" id="id15">Job module</a></li> <li><a class="reference internal" href="#persistent-configuration-storage" id="id15">Persistent configuration storage</a></li>
<li><a class="reference internal" href="#units-module" id="id16">Units module</a></li> <li><a class="reference internal" href="#material-info-lookup" id="id16">Material info lookup</a></li>
<li><a class="reference internal" href="#items-module" id="id17">Items module</a></li>
<li><a class="reference internal" href="#maps-module" id="id18">Maps module</a></li>
<li><a class="reference internal" href="#burrows-module" id="id19">Burrows module</a></li>
<li><a class="reference internal" href="#buildings-module" id="id20">Buildings module</a></li>
<li><a class="reference internal" href="#constructions-module" id="id21">Constructions module</a></li>
<li><a class="reference internal" href="#internal-api" id="id22">Internal API</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#core-interpreter-context" id="id23">Core interpreter context</a><ul> <li><a class="reference internal" href="#c-function-wrappers" id="id17">C++ function wrappers</a><ul>
<li><a class="reference internal" href="#event-type" id="id24">Event type</a></li> <li><a class="reference internal" href="#gui-module" id="id18">Gui module</a></li>
<li><a class="reference internal" href="#job-module" id="id19">Job module</a></li>
<li><a class="reference internal" href="#units-module" id="id20">Units module</a></li>
<li><a class="reference internal" href="#items-module" id="id21">Items module</a></li>
<li><a class="reference internal" href="#maps-module" id="id22">Maps module</a></li>
<li><a class="reference internal" href="#burrows-module" id="id23">Burrows module</a></li>
<li><a class="reference internal" href="#buildings-module" id="id24">Buildings module</a></li>
<li><a class="reference internal" href="#constructions-module" id="id25">Constructions module</a></li>
<li><a class="reference internal" href="#internal-api" id="id26">Internal API</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#core-interpreter-context" id="id27">Core interpreter context</a><ul>
<li><a class="reference internal" href="#event-type" id="id28">Event type</a></li>
</ul> </ul>
</li> </li>
<li><a class="reference internal" href="#plugins" id="id25">Plugins</a><ul> </ul>
<li><a class="reference internal" href="#burrows" id="id26">burrows</a></li> </li>
<li><a class="reference internal" href="#sort" id="id27">sort</a></li> <li><a class="reference internal" href="#plugins" id="id29">Plugins</a><ul>
<li><a class="reference internal" href="#burrows" id="id30">burrows</a></li>
<li><a class="reference internal" href="#sort" id="id31">sort</a></li>
</ul> </ul>
</li> </li>
</ul> </ul>
@ -717,10 +723,13 @@ should be prepared to catch the error and do the necessary
cleanup.</p> cleanup.</p>
</div> </div>
</div> </div>
<div class="section" id="dfhack-utilities"> <div class="section" id="dfhack-api">
<h1><a class="toc-backref" href="#id10">DFHack utilities</a></h1> <h1><a class="toc-backref" href="#id10">DFHack API</a></h1>
<p>DFHack utility functions are placed in the <tt class="docutils literal">dfhack</tt> global tree.</p> <p>DFHack utility functions are placed in the <tt class="docutils literal">dfhack</tt> global tree.</p>
<p>Currently it defines the following features:</p> <div class="section" id="native-utilities">
<h2><a class="toc-backref" href="#id11">Native utilities</a></h2>
<div class="section" id="input-output">
<h3><a class="toc-backref" href="#id12">Input &amp; Output</a></h3>
<ul> <ul>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.print(args...)</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.print(args...)</span></tt></p>
<p>Output tab-separated args as standard lua print would do, <p>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.</p>
string, global environment and command-line history file.</p> string, global environment and command-line history file.</p>
<p>If the interactive console is not accessible, returns <em>nil, error</em>.</p> <p>If the interactive console is not accessible, returns <em>nil, error</em>.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.pcall(f[,args...])</span></tt></p> </ul>
<p>Invokes f via xpcall, using an error function that attaches </div>
a stack trace to the error. The same function is used by SafeCall <div class="section" id="miscellaneous">
in C++, and dfhack.safecall.</p> <h3><a class="toc-backref" href="#id13">Miscellaneous</a></h3>
<p>The returned error is a table with separate <tt class="docutils literal">message</tt> and <ul>
<tt class="docutils literal">stacktrace</tt> string fields; it implements <tt class="docutils literal">__tostring</tt>.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">safecall(f[,args...])</span></tt>, <tt class="docutils literal"><span class="pre">dfhack.safecall(f[,args...])</span></tt></p>
<p>Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.saferesume(coroutine[,args...])</span></tt></p>
<p>Compares to coroutine.resume like dfhack.safecall vs pcall.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.run_script(name[,args...])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.run_script(name[,args...])</span></tt></p>
<p>Run a lua script in hack/scripts/, as if it was started from dfhack command-line. <p>Run a lua script in hack/scripts/, as if it was started from dfhack command-line.
The <tt class="docutils literal">name</tt> argument should be the name stem, as would be used on the command line. The <tt class="docutils literal">name</tt> 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.</p>
to group operations together in one big critical section. A plugin to group operations together in one big critical section. A plugin
can choose to run all lua code inside a C++-side suspend lock.</p> can choose to run all lua code inside a C++-side suspend lock.</p>
</li> </li>
</ul>
</div>
<div class="section" id="exception-handling">
<h3><a class="toc-backref" href="#id14">Exception handling</a></h3>
<ul>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.error(msg[,level[,verbose]])</span></tt></p>
<p>Throws a dfhack exception object with location and stack trace.
The verbose parameter controls whether the trace is printed by default.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">qerror(msg[,level])</span></tt></p>
<p>Calls <tt class="docutils literal">dfhack.error()</tt> with <tt class="docutils literal">verbose</tt> being <em>false</em>. Intended to
be used for user-caused errors in scripts, where stack traces are not
desirable.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.pcall(f[,args...])</span></tt></p>
<p>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.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">safecall(f[,args...])</span></tt>, <tt class="docutils literal"><span class="pre">dfhack.safecall(f[,args...])</span></tt></p>
<p>Just like pcall, but also prints the error using printerr before
returning. Intended as a convenience function.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.saferesume(coroutine[,args...])</span></tt></p>
<p>Compares to coroutine.resume like dfhack.safecall vs pcall.</p>
</li>
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.call_with_finalizer(num_cleanup_args,always,cleanup_fn[,cleanup_args...],fn[,args...])</span></tt></p>
<p>Invokes <tt class="docutils literal">fn</tt> with <tt class="docutils literal">args</tt>, and after it returns or throws an <p>Invokes <tt class="docutils literal">fn</tt> with <tt class="docutils literal">args</tt>, and after it returns or throws an
error calls <tt class="docutils literal">cleanup_fn</tt> with <tt class="docutils literal">cleanup_args</tt>. Any return values from error calls <tt class="docutils literal">cleanup_fn</tt> with <tt class="docutils literal">cleanup_args</tt>. Any return values from
@ -801,9 +827,40 @@ Implemented using <tt class="docutils literal"><span class="pre">call_with_final
<li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.with_temp_object(obj,fn[,args...])</span></tt></p> <li><p class="first"><tt class="docutils literal"><span class="pre">dfhack.with_temp_object(obj,fn[,args...])</span></tt></p>
<p>Calls <tt class="docutils literal"><span class="pre">fn(obj,args...)</span></tt>, then finalizes with <tt class="docutils literal">obj:delete()</tt>.</p> <p>Calls <tt class="docutils literal"><span class="pre">fn(obj,args...)</span></tt>, then finalizes with <tt class="docutils literal">obj:delete()</tt>.</p>
</li> </li>
<li><p class="first"><tt class="docutils literal">dfhack.exception</tt></p>
<p>Metatable of error objects used by dfhack. The objects have the
following properties:</p>
<dl class="docutils">
<dt><tt class="docutils literal">err.where</tt></dt>
<dd><p class="first last">The location prefix string, or <em>nil</em>.</p>
</dd>
<dt><tt class="docutils literal">err.message</tt></dt>
<dd><p class="first last">The base message string.</p>
</dd>
<dt><tt class="docutils literal">err.stacktrace</tt></dt>
<dd><p class="first last">The stack trace string, or <em>nil</em>.</p>
</dd>
<dt><tt class="docutils literal">err.cause</tt></dt>
<dd><p class="first last">A different exception object, or <em>nil</em>.</p>
</dd>
<dt><tt class="docutils literal">err.thread</tt></dt>
<dd><p class="first last">The coroutine that has thrown the exception.</p>
</dd>
<dt><tt class="docutils literal">err.verbose</tt></dt>
<dd><p class="first last">Boolean, or <em>nil</em>; specifies if where and stacktrace should be printed.</p>
</dd>
<dt><tt class="docutils literal">tostring(err)</tt>, or <tt class="docutils literal"><span class="pre">err:tostring([verbose])</span></tt></dt>
<dd><p class="first last">Converts the exception to string.</p>
</dd>
</dl>
</li>
<li><p class="first"><tt class="docutils literal">dfhack.exception.verbose</tt></p>
<p>The default value of the <tt class="docutils literal">verbose</tt> argument of <tt class="docutils literal">err:tostring()</tt>.</p>
</li>
</ul> </ul>
</div>
<div class="section" id="persistent-configuration-storage"> <div class="section" id="persistent-configuration-storage">
<h2><a class="toc-backref" href="#id11">Persistent configuration storage</a></h2> <h3><a class="toc-backref" href="#id15">Persistent configuration storage</a></h3>
<p>This api is intended for storing configuration options in the world itself. <p>This api is intended for storing configuration options in the world itself.
It probably should be restricted to data that is world-dependent.</p> It probably should be restricted to data that is world-dependent.</p>
<p>Entries are identified by a string <tt class="docutils literal">key</tt>, but it is also possible to manage <p>Entries are identified by a string <tt class="docutils literal">key</tt>, 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.</p> However, currently every entry has a 180+-byte dead-weight overhead.</p>
</div> </div>
<div class="section" id="material-info-lookup"> <div class="section" id="material-info-lookup">
<h2><a class="toc-backref" href="#id12">Material info lookup</a></h2> <h3><a class="toc-backref" href="#id16">Material info lookup</a></h3>
<p>A material info record has fields:</p> <p>A material info record has fields:</p>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">type</tt>, <tt class="docutils literal">index</tt>, <tt class="docutils literal">material</tt></p> <li><p class="first"><tt class="docutils literal">type</tt>, <tt class="docutils literal">index</tt>, <tt class="docutils literal">material</tt></p>
@ -881,8 +938,9 @@ Accept dfhack_material_category auto-assign table.</p>
</li> </li>
</ul> </ul>
</div> </div>
</div>
<div class="section" id="c-function-wrappers"> <div class="section" id="c-function-wrappers">
<h2><a class="toc-backref" href="#id13">C++ function wrappers</a></h2> <h2><a class="toc-backref" href="#id17">C++ function wrappers</a></h2>
<p>Thin wrappers around C++ functions, similar to the ones for virtual methods. <p>Thin wrappers around C++ functions, similar to the ones for virtual methods.
One notable difference is that these explicit wrappers allow argument count One notable difference is that these explicit wrappers allow argument count
adjustment according to the usual lua rules, so trailing false/nil arguments adjustment according to the usual lua rules, so trailing false/nil arguments
@ -911,7 +969,7 @@ can be omitted.</p>
</li> </li>
</ul> </ul>
<div class="section" id="gui-module"> <div class="section" id="gui-module">
<h3><a class="toc-backref" href="#id14">Gui module</a></h3> <h3><a class="toc-backref" href="#id18">Gui module</a></h3>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.gui.getCurViewscreen()</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.gui.getCurViewscreen()</tt></p>
<p>Returns the viewscreen that is current in the core.</p> <p>Returns the viewscreen that is current in the core.</p>
@ -947,7 +1005,7 @@ The is_bright boolean actually seems to invert the brightness.</p>
</ul> </ul>
</div> </div>
<div class="section" id="job-module"> <div class="section" id="job-module">
<h3><a class="toc-backref" href="#id15">Job module</a></h3> <h3><a class="toc-backref" href="#id19">Job module</a></h3>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.job.cloneJobStruct(job)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.job.cloneJobStruct(job)</tt></p>
<p>Creates a deep copy of the given job.</p> <p>Creates a deep copy of the given job.</p>
@ -984,7 +1042,7 @@ a lua list containing them.</p>
</ul> </ul>
</div> </div>
<div class="section" id="units-module"> <div class="section" id="units-module">
<h3><a class="toc-backref" href="#id16">Units module</a></h3> <h3><a class="toc-backref" href="#id20">Units module</a></h3>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.units.getPosition(unit)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.units.getPosition(unit)</tt></p>
<p>Returns true <em>x,y,z</em> of the unit, or <em>nil</em> if invalid; may be not equal to unit.pos if caged.</p> <p>Returns true <em>x,y,z</em> of the unit, or <em>nil</em> if invalid; may be not equal to unit.pos if caged.</p>
@ -1038,7 +1096,7 @@ or raws. The <tt class="docutils literal">ignore_noble</tt> boolean disables the
</ul> </ul>
</div> </div>
<div class="section" id="items-module"> <div class="section" id="items-module">
<h3><a class="toc-backref" href="#id17">Items module</a></h3> <h3><a class="toc-backref" href="#id21">Items module</a></h3>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.items.getPosition(item)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.items.getPosition(item)</tt></p>
<p>Returns true <em>x,y,z</em> of the item, or <em>nil</em> if invalid; may be not equal to item.pos if in inventory.</p> <p>Returns true <em>x,y,z</em> of the item, or <em>nil</em> if invalid; may be not equal to item.pos if in inventory.</p>
@ -1081,7 +1139,7 @@ Returns <em>false</em> in case of error.</p>
</ul> </ul>
</div> </div>
<div class="section" id="maps-module"> <div class="section" id="maps-module">
<h3><a class="toc-backref" href="#id18">Maps module</a></h3> <h3><a class="toc-backref" href="#id22">Maps module</a></h3>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.maps.getSize()</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.maps.getSize()</tt></p>
<p>Returns map size in blocks: <em>x, y, z</em></p> <p>Returns map size in blocks: <em>x, y, z</em></p>
@ -1122,7 +1180,7 @@ burrows, or the presence of invaders.</p>
</ul> </ul>
</div> </div>
<div class="section" id="burrows-module"> <div class="section" id="burrows-module">
<h3><a class="toc-backref" href="#id19">Burrows module</a></h3> <h3><a class="toc-backref" href="#id23">Burrows module</a></h3>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.burrows.findByName(name)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.burrows.findByName(name)</tt></p>
<p>Returns the burrow pointer or <em>nil</em>.</p> <p>Returns the burrow pointer or <em>nil</em>.</p>
@ -1157,7 +1215,7 @@ burrows, or the presence of invaders.</p>
</ul> </ul>
</div> </div>
<div class="section" id="buildings-module"> <div class="section" id="buildings-module">
<h3><a class="toc-backref" href="#id20">Buildings module</a></h3> <h3><a class="toc-backref" href="#id24">Buildings module</a></h3>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.buildings.getSize(building)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.buildings.getSize(building)</tt></p>
<p>Returns <em>width, height, centerx, centery</em>.</p> <p>Returns <em>width, height, centerx, centery</em>.</p>
@ -1297,7 +1355,7 @@ can be determined this way, <tt class="docutils literal">constructBuilding</tt>
</ul> </ul>
</div> </div>
<div class="section" id="constructions-module"> <div class="section" id="constructions-module">
<h3><a class="toc-backref" href="#id21">Constructions module</a></h3> <h3><a class="toc-backref" href="#id25">Constructions module</a></h3>
<ul> <ul>
<li><p class="first"><tt class="docutils literal">dfhack.constructions.designateNew(pos,type,item_type,mat_index)</tt></p> <li><p class="first"><tt class="docutils literal">dfhack.constructions.designateNew(pos,type,item_type,mat_index)</tt></p>
<p>Designates a new construction at given position. If there already is <p>Designates a new construction at given position. If there already is
@ -1313,7 +1371,7 @@ Returns <em>true, was_only_planned</em> if removed; or <em>false</em> if none fo
</ul> </ul>
</div> </div>
<div class="section" id="internal-api"> <div class="section" id="internal-api">
<h3><a class="toc-backref" href="#id22">Internal API</a></h3> <h3><a class="toc-backref" href="#id26">Internal API</a></h3>
<p>These functions are intended for the use by dfhack developers, <p>These functions are intended for the use by dfhack developers,
and are only documented here for completeness:</p> and are only documented here for completeness:</p>
<ul> <ul>
@ -1356,7 +1414,7 @@ Returns: <em>found_index</em>, or <em>nil</em> if end reached.</p>
</div> </div>
</div> </div>
<div class="section" id="core-interpreter-context"> <div class="section" id="core-interpreter-context">
<h2><a class="toc-backref" href="#id23">Core interpreter context</a></h2> <h2><a class="toc-backref" href="#id27">Core interpreter context</a></h2>
<p>While plugins can create any number of interpreter instances, <p>While plugins can create any number of interpreter instances,
there is one special context managed by dfhack core. It is the there is one special context managed by dfhack core. It is the
only context that can receive events from DF and plugins.</p> only context that can receive events from DF and plugins.</p>
@ -1387,7 +1445,7 @@ Using <tt class="docutils literal">timeout_active(id,nil)</tt> cancels the timer
</li> </li>
</ul> </ul>
<div class="section" id="event-type"> <div class="section" id="event-type">
<h3><a class="toc-backref" href="#id24">Event type</a></h3> <h3><a class="toc-backref" href="#id28">Event type</a></h3>
<p>An event is just a lua table with a predefined metatable that <p>An event is just a lua table with a predefined metatable that
contains a __call metamethod. When it is invoked, it loops contains a __call metamethod. When it is invoked, it loops
through the table with next and calls all contained values. through the table with next and calls all contained values.
@ -1413,14 +1471,14 @@ order using <tt class="docutils literal">dfhack.safecall</tt>.</p>
</div> </div>
</div> </div>
<div class="section" id="plugins"> <div class="section" id="plugins">
<h1><a class="toc-backref" href="#id25">Plugins</a></h1> <h1><a class="toc-backref" href="#id29">Plugins</a></h1>
<p>DFHack plugins may export native functions and events <p>DFHack plugins may export native functions and events
to lua contexts. They are automatically imported by to lua contexts. They are automatically imported by
<tt class="docutils literal"><span class="pre">mkmodule('plugins.&lt;name&gt;')</span></tt>; this means that a lua <tt class="docutils literal"><span class="pre">mkmodule('plugins.&lt;name&gt;')</span></tt>; this means that a lua
module file is still necessary for <tt class="docutils literal">require</tt> to read.</p> module file is still necessary for <tt class="docutils literal">require</tt> to read.</p>
<p>The following plugins have lua support.</p> <p>The following plugins have lua support.</p>
<div class="section" id="burrows"> <div class="section" id="burrows">
<h2><a class="toc-backref" href="#id26">burrows</a></h2> <h2><a class="toc-backref" href="#id30">burrows</a></h2>
<p>Implements extended burrow manipulations.</p> <p>Implements extended burrow manipulations.</p>
<p>Events:</p> <p>Events:</p>
<ul> <ul>
@ -1458,7 +1516,7 @@ set is the same as used by the command line.</p>
<p>The lua module file also re-exports functions from <tt class="docutils literal">dfhack.burrows</tt>.</p> <p>The lua module file also re-exports functions from <tt class="docutils literal">dfhack.burrows</tt>.</p>
</div> </div>
<div class="section" id="sort"> <div class="section" id="sort">
<h2><a class="toc-backref" href="#id27">sort</a></h2> <h2><a class="toc-backref" href="#id31">sort</a></h2>
<p>Does not export any native functions as of now. Instead, it <p>Does not export any native functions as of now. Instead, it
calls lua code to perform the actual ordering of list items.</p> calls lua code to perform the actual ordering of list items.</p>
</div> </div>

@ -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 // Create a new exception for this thread
lua_newtable(L); lua_newtable(L);
luaL_where(L, 1); luaL_where(L, slevel);
lua_setfield(L, -2, "where");
lua_pushstring(L, "coroutine resume failed"); lua_pushstring(L, "coroutine resume failed");
lua_concat(L, 2);
lua_setfield(L, -2, "message"); lua_setfield(L, -2, "message");
lua_getfield(L, -2, "verbose");
lua_setfield(L, -2, "verbose");
lua_swap(L); lua_swap(L);
lua_setfield(L, -2, "cause"); lua_setfield(L, -2, "cause");
} }
@ -483,12 +485,57 @@ static int dfhack_onerror(lua_State *L)
return 1; 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) static int dfhack_exception_tostring(lua_State *L)
{ {
luaL_checktype(L, 1, LUA_TTABLE); 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); 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"); lua_getfield(L, 1, "message");
if (!lua_isstring(L, -1)) 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, "(error message is not a string)");
} }
lua_pushstring(L, "\n"); if (verbose)
lua_getfield(L, 1, "stacktrace"); {
if (!lua_isstring(L, -1)) lua_pushstring(L, "\n");
lua_pop(L, 2); lua_getfield(L, 1, "stacktrace");
if (!lua_isstring(L, -1))
lua_pop(L, 2);
}
lua_pushstring(L, "\ncaused by:\n"); lua_pushstring(L, "\ncaused by:\n");
lua_getfield(L, 1, "cause"); lua_getfield(L, 1, "cause");
if (lua_isnil(L, -1)) if (lua_isnil(L, -1))
lua_pop(L, 2); 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 else
error_tostring(L); error_tostring(L);
@ -655,7 +713,12 @@ static int dfhack_coauxwrap (lua_State *L) {
if (Lua::IsSuccess(r)) if (Lua::IsSuccess(r))
return lua_gettop(L); return lua_gettop(L);
else else
{
if (lua_checkstack(L, LUA_MINSTACK))
convert_to_exception(L, 1);
return lua_error(L); return lua_error(L);
}
} }
static int dfhack_cowrap (lua_State *L) { static int dfhack_cowrap (lua_State *L) {
@ -1162,6 +1225,7 @@ static const luaL_Reg dfhack_funcs[] = {
{ "safecall", dfhack_safecall }, { "safecall", dfhack_safecall },
{ "saferesume", dfhack_saferesume }, { "saferesume", dfhack_saferesume },
{ "onerror", dfhack_onerror }, { "onerror", dfhack_onerror },
{ "error", dfhack_error },
{ "call_with_finalizer", dfhack_call_with_finalizer }, { "call_with_finalizer", dfhack_call_with_finalizer },
{ "with_suspend", lua_dfhack_with_suspend }, { "with_suspend", lua_dfhack_with_suspend },
{ "open_plugin", dfhack_open_plugin }, { "open_plugin", dfhack_open_plugin },
@ -1362,6 +1426,8 @@ lua_State *DFHack::Lua::Open(color_ostream &out, lua_State *state)
lua_newtable(state); lua_newtable(state);
lua_pushcfunction(state, dfhack_exception_tostring); lua_pushcfunction(state, dfhack_exception_tostring);
lua_setfield(state, -2, "__tostring"); lua_setfield(state, -2, "__tostring");
lua_pushcfunction(state, dfhack_exception_tostring);
lua_setfield(state, -2, "tostring");
lua_dup(state); lua_dup(state);
lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN); lua_rawsetp(state, LUA_REGISTRYINDEX, &DFHACK_EXCEPTION_META_TOKEN);
lua_setfield(state, -2, "exception"); lua_setfield(state, -2, "exception");

@ -49,6 +49,10 @@ function dfhack.pcall(f, ...)
return xpcall(f, dfhack.onerror, ...) return xpcall(f, dfhack.onerror, ...)
end end
function qerror(msg, level)
dfhack.error(msg, (level or 1) + 1, false)
end
function dfhack.with_finalize(...) function dfhack.with_finalize(...)
return dfhack.call_with_finalizer(0,true,...) return dfhack.call_with_finalizer(0,true,...)
end end

@ -235,7 +235,7 @@ function found_offset(name,val)
if not val then if not val then
print('Could not find offset '..name) print('Could not find offset '..name)
if not cval and not utils.prompt_yes_no('Continue with the script?') then if not cval and not utils.prompt_yes_no('Continue with the script?') then
error('User quit') qerror('User quit')
end end
return return
end end

@ -379,7 +379,7 @@ function prompt_yes_no(msg,default)
elseif string.match(rv,'^[Nn]') then elseif string.match(rv,'^[Nn]') then
return false return false
elseif rv == 'abort' then 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 elseif rv == '' and default ~= nil then
return default return default
end end

@ -43,12 +43,12 @@ end
local data = ms.get_data_segment() local data = ms.get_data_segment()
if not data then if not data then
error('Could not find data segment') qerror('Could not find data segment')
end end
print('\nData section: '..tostring(data)) print('\nData section: '..tostring(data))
if data.size < 5000000 then if data.size < 5000000 then
error('Data segment too short.') qerror('Data segment too short.')
end end
local searcher = ms.DiffSearcher.new(data) local searcher = ms.DiffSearcher.new(data)
@ -103,7 +103,7 @@ local function exec_finder(finder, names)
if not dfhack.safecall(finder) then if not dfhack.safecall(finder) then
if not utils.prompt_yes_no('Proceed with the rest of the script?') then if not utils.prompt_yes_no('Proceed with the rest of the script?') then
searcher:reset() searcher:reset()
error('Quit') qerror('Quit')
end end
end end
else else

@ -116,8 +116,7 @@ if opt then
if opt == '--fix' then if opt == '--fix' then
fix = true fix = true
else else
dfhack.printerr('Invalid option: '..opt) qerror('Invalid option: '..opt)
return
end end
end end

@ -1,8 +1,7 @@
-- Makes the game immediately save the state. -- Makes the game immediately save the state.
if not dfhack.isMapLoaded() then if not dfhack.isMapLoaded() then
dfhack.printerr("World and map aren't loaded.") qerror("World and map aren't loaded.")
return
end end
local ui_main = df.global.ui.main local ui_main = df.global.ui.main