Skip to content

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.