.. include:: ../global.rst .. _`condition handlers`: Condition Handlers ------------------ Handling errors in :lname:`Idio` works differently to many programming languages. In those, when an exception is raised then the computation state is *unwound* to where the handler is and the handler is executed and control continues from after the exception handling block. Think ``try``/``except`` in :lname:`Python`. In :lname:`Idio`, when a condition is raised the VM *pauses* the current computation with all of the extant computation intact and walks back through the execution state looking to see if any handlers have been established for this condition type (or an ancestor of this condition type). If one is found the associated *handler* is called with the condition as an argument which can do a number of things: #. it can return a value in which case that value is used *in place of* the errant computation and the original computation continues as if the errant computation had returned whatever the handler has just returned This isn't used by the :lname:`Idio` defined error handlers as the conditions are not normally something easily recoverable from. There is an example below of unwise intervention. It also requires that the handler have some intimate knowledge of the state of processing at the time of the error. Suppose you wrote an :ref:`^i/o-no-such-file-error <^i/o-no-such-file-error>` handler around a call to :ref:`open-file `. The expectation of the calling code is to receive an open file handle and so, if you want to handle the missing file by "quickly creating and opening another file" in its stead to return the open file handle, you also need to return the correct type of file (input or output) and with the correct mode. #. it can raise the condition again with a view that a previously established handler can take care of the problem The state computation is unchanged, the outer handler can still return a value. #. it can raise a totally different condition The state computation is unchanged, the outer handler, handling a different condition, can still return a value to the original point of failure. #. it can emulate the execution stack unwinding (``try``/``except``) by calling :ref:`trap-return ` in the handler ---- You can establish handlers in two ways: #. :ref:`trap ` establishes a handler for a condition type (or types) around a block of code #. :ref:`set-default-handler! ` establishes a handler for a condition type In that sense, ``set-default-handler!`` is much more like the shell's ``trap`` builtin. If you don't do anything you will find there are several handlers established by default, including: * an :ref:`^rt-command-status-error <^rt-command-status-error>` handler, :ref:`default-rcse-handler ` If an external program fails then this handler will normally cause :program:`idio` to exit in the same manner. There are some mitigations. * a :ref:`^condition <^condition>` handler, :ref:`default-condition-handler ` This looks to see if a handler for the condition's type has been established with ``set-default-handler!`` and, if so, runs it. * a :ref:`^condition <^condition>` handler, :ref:`restart-condition-handler ` This attempts to unwind the current state of execution to the most recent top level expression and runs its continuation. * a :ref:`^condition <^condition>` handler, :ref:`reset-condition-handler ` This attempts to exit cleanly. In addition, every top-level expression is wrapped in an **ABORT** continuation allowing Idio to unwind that expression. .. attention:: This should be straight-forward but somehow isn't. Example Handler ^^^^^^^^^^^^^^^ Suppose we want to handle :ref:`^rt-divide-by-zero-error <^rt-divide-by-zero-error>`: .. code-block:: idio trap ^rt-divide-by-zero-error (function (c) { ; we could generate a scathing report with ; condition-report "fool!" c ; return a value indicating the ; user's foolishness 'fool }) { 1 / 0 } .. code-block:: console fool Hmm, nothing. Well, technically, the condition handler will have returned the symbol ``fool`` *in place of* the expression ``1 / 0`` which itself is returned by the ``trap`` expression. Suppose the `body` was more complex and went on to use the returned value: .. code-block:: idio trap ^rt-divide-by-zero-error (function (c) { 'fool }) { t := 1 / 0 1 + t } .. code-block:: console default-condition-handler:[35842]:*stdin*:line 1:binary-+:^rt-parameter-type-error:bad parameter type: 'fool' a symbol is not a number debug is #: debugger invocation failed restart-condition-handler:[35842]:*stdin*:line 1:binary-+:^rt-parameter-type-error:bad parameter type: 'fool' a symbol is not a number restart-condition-handler: restoring ABORT continuation #2: "ABORT to toplevel (PC [28]@98)" # This shows our handler as being incredibly naïve in what it returns as a value as now we get an :ref:`^rt-parameter-type-error <^rt-parameter-type-error>` in the next expression as the addition, ``+``, won't accept the symbol as a valid type. There's a bit more going on there as we bounce through :ref:`default-condition-handler `, fail to run the debugger, then throw the condition to :ref:`restart-condition-handler ` which (unhelpfully) repeats the same message and then invokes the **ABORT** continuation which returns ``#`` to the REPL. All at the point of the ``1 / 0`` expression. We can revert to the more common ``try``/``expect`` behaviour by returning from the ``trap`` itself with :ref:`trap-return `: .. code-block:: idio trap ^rt-divide-by-zero-error (function (c) { trap-return 'fool }) { t := 1 / 0 1 + t } .. code-block:: console fool Here, we return the symbol ``fool`` from ``trap`` as soon as the divide-by-zero error occurs and without stumbling into the problem with addition. Handler Functions ^^^^^^^^^^^^^^^^^ .. _`trap`: .. idio:template:: trap conditions handler body Establish a handler `handler` for conditions `conditions` around `body` :param conditions: a condition type or a list of condition types :type conditions: symbol or a list of symbols :param handler: the handler :type handler: 1-ary function :param body: the body :type body: expression ``trap`` may not return. Normally, ``trap`` will return whatever `body` returns -- usually `body` is a :ref:`block ` and so the value of the last expression evaluated. If a condition is raised during the processing of `body` then ``trap`` may (eventually) continue processing `body` if a handler returns a value in place of the condition-raising expression. ``trap`` will therefore return the value of the last expression evaluated, as normal. The condition handler may be :ref:`restart-condition-handler ` in which case the current top-level expression is discarded and its continuation is run. The condition handler may be :ref:`reset-condition-handler ` in which case the :program:`idio` process will attempt to exit. .. _`trap-return`: .. idio:template:: trap-return [v] return `v` to the continuation of :ref:`trap ` :param v: the value to return, defaults to ``#`` :type v: any, optional .. _`suppress-errors!`: .. idio:template:: suppress-errors! conditions body Establish a handler (that returns ``#``) for conditions `conditions` around `body` :param conditions: a condition type or a list of condition types :type conditions: a condition type or a list of condition types :param body: the body expression :type body: expression .. attention:: ``suppress-errors!`` has a very narrow use case. In general, returning a value to failed computations requires intimate knowledge of the computation. However, ``suppress-errors!`` is used exclusively with :ref:`^system-error <^system-error>` in situations where there is a race condition between the parent and child after a :ref:`libc/fork ` which results in one or the other failing. Standard Handlers ^^^^^^^^^^^^^^^^^ .. _`default-SIGCHLD-handler`: .. idio:function:: default-SIGCHLD-handler c The default handler for an ``^rt-signal-SIGCHLD`` condition This invokes :ref:`do-job-notification `. :param c: the condition :type c: condition instance .. _`default-racse-handler`: .. idio:function:: default-racse-handler c The default handler for an ``^rt-async-command-status-error`` condition :param c: the condition :type c: condition instance :return: ``#`` The default behaviour is to report but otherwise ignore failed asynchronous processes .. seealso:: :ref:`suppress-async-command-report! ` for means to change the default behaviour. .. _`default-rcse-handler`: .. idio:function:: default-rcse-handler c The default handler for an ``^rt-command-status-error`` condition :param c: the condition :type c: condition instance :return: see below If the command exits with a non-zero status (from :manpage:`exit(3)` or by signal) then we exit the same way. Otherwise ``#`` .. seealso:: :ref:`suppress-exit-on-error! ` and :ref:`suppress-pipefail! ` for means to change the default behaviour. .. _`default-condition-handler`: .. idio:function:: default-condition-handler c Invoke the default handler for condition `c` If there is no default handler: - if the session is interactive then the debugger is invoked - otherwise the condition is re-raised :param c: the condition :type c: condition instance does not return per se .. _`set-default-handler!`: .. idio:function:: set-default-handler! ct handler set the default handler for condition type `ct` to `handler` If a condition of type `ct` is not otherwise handled then `handler` will be invoked with the continuation. :param ct: condition type :type ct: condition type :param handler: handler for the condition type :type handler: function :return: ``#`` .. _`clear-default-handler!`: .. idio:function:: clear-default-handler! ct unset the default handler for condition type `ct` The default behaviour for conditions of type `ct` will resume. :param ct: condition type :type ct: condition type :return: ``#`` .. _`restart-condition-handler`: .. idio:function:: restart-condition-handler c Restart the VM with the continuation of the current top-level expression. :param c: the condition :type c: condition instance does not return per se .. warning:: ``restart-condition-handler`` is only effective if the session is interactive .. _`reset-condition-handler`: .. idio:function:: reset-condition-handler c Stop the VM and exit non-zero. :param c: the condition :type c: condition instance Does not return. .. include:: ../commit.rst