Errors
Errors
Computer programs written in any language may encounter unexpected or challenging situations. The user might request that the program opens a file, for example, but the file doesn't exist. An algorithm might call for the solution of a linear system, but the matrix turns out to be poorly conditioned. Both these examples are foreseeable by the programmer at the time of writing, so an appropriate course of action can be taken by the program. Perhaps the user should be informed that the file didn't exist or that the algorithm didn't succeed, or perhaps an alternative algorithm is available.
There are three kinds of error in Morpho:
-
Compilation errors are thrown when the provided code is incorrect. If the Morpho compiler can't find a symbol, it throws
SymblUndf, for example. Compilation errors prevent the code from running at all; they must be fixed by the programmer rather than handled by the code. -
Runtime errors are thrown during execution of the program and indicate that execution cannot proceed further. Execution therefore halts, and the runtime environment displays a message describing the error and where it occurred.
-
Runtime warnings. Occasionally, a situation occurs that isn't strictly an error, but something is unusual that the user should be told about. An algorithm that solves a problem may wish to report that the quality of the solution is poorer than expected. Or perhaps the user used deprecated functionality, and the program wishes to suggest an alternative. Warnings do not interrupt execution; they simply display a highlighted message to the user.
All errors in Morpho have two components: a short tag that identifies
the error, e.g. SymblUndf, and a longer description. This structure
is intended to support internationalization, because locale-appropriate
descriptions could be loaded, and also support a need for customized
error messages that can include useful information.
Errors are instances of the Error class, which can be used to create
new errors. You supply the tag and a default error message in the
constructor
var myErr = Error("MyTag", "Default error message")
Once the error is created, call the throw method to throw the error
myErr.throw()
Any error can also be used as a warning as described above by using
the warning method
myErr.warning()
You can also call throw or warning with a custom message provided as
a String, which is useful if you want to provide more information to the
user about what happened
myErr.throw("File ${foo} was missing.")
Handling errors
While not necessarily expected, some errors are at least foreseeable. A well written program should handle such errors gracefully, and provide an alternate course of action if possible. If the user requests a file that isn't available, the program can notify them of this fact and request a different file, for example.
Morpho provides a control structure to handle errors using the try and
catch keywords
var f
try {
f = File(fname, "r")
} catch {
"FlOpnFld":
print "File ${fname} not found"
}
The try statement describes code to be executed that is anticipated
may throw an error. The catch statement defines an error handler, a
collection of errors that can be handled and code to handle them. You
provide the appropriate tag for each error to handle, here just
FlOpnFld, and a corresponding statement to execute if and only if the
error is generated. You may use the break keyword within a catch
statement, which allows you to escape from the error handler entirely.
If the code in the try block generates FlOpnFld, control is
transferred to the corresponding catch statement, which in this case
prints an error. If the try statement doesn't thrown any errors, code
in the catch statement isn't executed.
What if the try block throws an error that isn't handled by the
corresponding catch statement? Error handlers work on a stack, so
every time try is executed, the Morpho runtime adds the
corresponding handler to the stack; once the try ... catch statement
is finished executing the error handler is removed from the stack. More
than one error handler can be active if try ... catch statements are
nested, for example, or if a function called within a try statement
provides its own error handler. In any case, when an error is thrown,
the Morpho runtime looks at the most recent error handler, i.e. the
handler from the most recently executed try statement. If the error
can be handled by the handler, then control is transferred to it; if
not, the runtime walks back along the stack of error handlers until one
is found that can handle the error. If no suitable handler is found,
the error is reported the user and interrupts execution as in the
absence of try.
Because error handlers can transfer control very non-locally jumping outside of multiple nested function or method calls in extreme cases they should be used with care. It's a good idea to document errors each function might throw, and to consider carefully possible errors that might need to be handled by your code.
Note that Morpho does not provide a "default" error handler, i.e.
a section of a catch block that catches all errors. This is because
the intent of try ... catch is to handle foreseen errors only.