13.2 How to organise code to use MBFL exceptions

Effective use of MBFL’s exception–handling is intertwined with the location mechanism, Running location handlers.

Exceptional conditions are described by instances of appropriate exceptional–condition classes, Exceptional–condition objects. It is mandatory to use only exceptional–condition objects whose class is a subclass of mbfl_exceptional_condition; no other Bash value is allowed.

Whenever an exceptional–condition object is “raised” an exception–handling function is called to react to it; such function is called exception–handler. To raise an exceptional–condition we apply the function mbfl_exception_raise() to the object’s data variable:

if ! try_an_action 1 2 3
then
    mbfl_default_object_declare(CND)

    mbfl_runtime_error_condition_make _(CND) $FUNCNAME 'failed action'
    mbfl_exception_raise _(CND)
fi

MBFL allows us to push exception–handlers onto a global stack, in such a way that handlers are made “local” by removing them upon leaving the current location. Here is how to write a function:

function do_some_thing () {
    mbfl_location_enter
    {
        mbfl_exception_handler 'handle_some_exceptions'
        # do some thing
    }
    mbfl_location_leave
}

while “do some thing” code is running: if an exception is raised, the exception–handler handle_some_exceptions() is applied to the exceptional–condition object; upon leaving the location handle_some_exceptions() is popped from the stack.

Compared to other languages: MBFL’s exception–handling is peculiar because, under Bash, there is no automatic call stack unwinding; when needed we must return from all the functions until we reach a “beginning of task” function. We must partition the code into an appropriate number of nested functions. We can organise code as follows:

function main () {
    declare -i RETURN_STATUS

    perform_an_operation
    RETURN_STATUS=$?
    if (( 0 != RETURN_STATUS ))
    then exit_failure
    fi
    exit_success
}
function perform_an_operation () {
    mbfl_location_enter
    {
        mbfl_exception_handler 'an_exception_handler'

        if ! operation_step_one
        then
            mbfl_location_leave
            return_because_reason
        fi

        if ! operation_step_two
        then
            mbfl_location_leave
            return_because_another_reason
        fi
    }
    mbfl_location_leave
    return_success
}
function operation_step_one () {
    mbfl_location_enter
    {
        mbfl_exception_handler 'another_exception_handler'

        if ! do_some_thing
        then
            if ! raise_an_exception
            then
                mbfl_location_leave
                return_because_bad_reason
            fi
        fi

        if ! do_some_other_thing
        then
            if ! raise_another_exception
            then
                mbfl_location_leave
                return_because_another_bad_reason
            fi
        fi
    }
    mbfl_location_leave
    return_success
}

We can push any number of handlers in each location:

function perform_an_operation () {
    mbfl_location_enter
    {
        mbfl_exception_handler 'an_exception_handler'
        mbfl_exception_handler 'another_exception_handler'
        # do some thing
    }
    mbfl_location_leave
    return_success
}

whenever an exceptional–condition is raised: the topmost handler is applied to the object; it might:

in the last case: the next handler from the stack is applied to the object and so on until some handler handles the exception. A default handler is pushed to the stack upon MBFL loading–time: if it is called, it might terminate the script with a “uncaught exception” exit status.


This document describes version 3.0.0-devel.9 of Marcos Bash Functions Library.