------------------------------------------------------------------------------
MC logo
Toms Lisp Exception Handling
[^] Tom's Lisp
------------------------------------------------------------------------------
[Basic Input Format] [Lists, Pairs and Related Operations] [Conditional Evaluation] [Basic Function Definition] [Definitions and Scope] [Functions Which Take Functions] [String Functions] [Exception Handling] [Quoting And Evaluation] [Variable-Length Parameter Lists] [Macro Definitions] [Printing] [The Tomslsp command and its switches] [Index of Standard Functions]

Tom's lisp supports a simple exception mechanism. Exceptions can be generated by the system or the user, and there are several ways to catch them.

Exceptions

Each exception has a unique integer code number; there is no hierarchy of exceptions as in Java or C++. The the following exceptions are predefined:
Code   Symbolic Name   Description
1   ERR_UNDEF   Undefined identifier. This is thrown when you evaluate an identifier which has no value.
2   ERR_BADOP   Inappropriate type in an operation. Presently, the only way to get this one is to perform apply on something which is not a function, usually because you typed a list like (1 2 3) and forgot to quote it, so you were trying to apply on the integer 1. Many things which sound like the name of this one actually belong to ERR_BADTYPE.
3   ERR_BADARG   The argument list was badly-formed, for instance (+ 2 3 . 5), where the argument list is not a proper list.
4   ERR_SHORTARG   A function was called with too few arguments.
5   ERR_BADTYPE   A built-in function was called with the wrong argument type, such as taking the car of a non-list. Only built-in calls can generate this; lambda functions and macros can be called with any argument types. Of course, if they are called with types they cannot handle, their primitives may throw ERR_BADTYPE.
6   ERR_LONGARG   A function was called with too many arguments.
7   ERR_RPAREN   Syntax error: Right paren expected.
8   ERR_SYN   General syntax error.
9   ERR_OPFAIL   File open failed. The load operation can throw this one.
10   ERR_DIVZERO   Division by zero.
11   ERR_INTER   Operation interrupted. This is thrown when the process gets the SIGINT or SIGTERM signals. These are sent by the keyboard ^C or the kill command on Unix systems.
12   ERR_MISC   The miscellaneous error. This is an extra for use by applications, and is not thrown by the Tom's Lisp system.
13   ERR_ABORT   This is thrown by the abort primitive. It is not thrown by the Tom's Lisp system.

Error Objects

An exception is thrown when an operation returns an error object. An exception object contains a code number from the above list, and a string which describes the error. When an error occurs, the value of the errant expression is an error object.
lsp>(/ 5 0)
**** Error 10: Division by zero. ****
   (/ 5 0)
Here, we are performing the division operation on 5 and 0. The result is not a number, but an error object containing the code 10 and the appropriate division by zero message. In a very real sense, the system is acting just as it does for a successful division, evaluating the expression and printing the result. The result just isn't a number. Also, Tom's Lisp prints lots of nice asterisks when it prints an error object.

Error objects are special in more ways than print format, however. Any evaluation which sees an error object stops whatever it was doing and returns the error. Therefore, once an error is created, all pending operations return, passing the error object up to the top level.

lsp>(cons (+ 1 (/ 5 0))  '(5 9 1 4))
**** Error 10: Division by zero. ****
   (cons (+ 1 (/ 5 0)) (quote (5 9 1 4)))
   (+ 1 (/ 5 0))
   (/ 5 0)
Here, the division returned an error object. The addition saw an error object as its second parameter, so it returned it. The cons operation saw the error object as its first parameter, so it returned that. The value of cons was the error object, which Tom's Lisp printed. A summary of the canceled operations is printed with the error object.

If the system errors are not enough for you, you can create your own. The error primitive takes an integer and a string and returns the error object containing those. There is also an abort primitive which is just a shorthand for error.

lsp>(error 98 "Sorry, Out Of Order")
**** Error 98: Sorry, Out Of Order ****
   (error 98 "Sorry, Out Of Order")
lsp>(list 1 4 9 8 7 (error 34 "Hi there!") 23 9)      
**** Error 34: Hi there! ****
   (list 1 4 9 8 7 (error 34 "Hi there!") 23 9)
   (error 34 "Hi there!")
lsp>(cons 15 (list 3 9 1 (abort) 2))
**** Error 13: Aborted ****
   (cons 15 (list 3 9 1 (abort) 2))
   (list 3 9 1 (abort) 2)
   (abort)
   (error 13 "Aborted")

Rather than choosing your own exception numbers, you should allocate them with the errcode operation. It simply uses a counter to assign the next number for your favorite error message name.

lsp>(errcode ERR_SMELLY)
14
Note thatlsp>(+ 17 (error ERR_SMELLY "Took shoes off"))                
**** Error 14: Took shoes off ****
   (+ 17 (error ERR_SMELLY "Took shoes off"))
   (error 14 "Took shoes off")

Catching Errors

The rule that any operation which sees an error stops and returns it is violated by a small number of operations which can catch exceptions. The simplest of these are succeeds and fails. These take any number of expressions and evaluate them left to right until all are done or one throws an exception. They then return the appropriate boolean, where “success” means completing without throwing an exception.
lsp>(succeeds (/ 8 9) (/ 3 4) (/ 2 1) (/ 10 4))
#t
lsp>(succeeds (/ 8 9) (/ 3 4) (/ 2 0) (/ 10 4))
nil
lsp>(fails (car 17))
#t

A more flexible and familiar form is try. It looks like this:

(try expr (code1 expr1) . . . (coden exprn) [ (#t exprd) ] )
The form evaluates expr. If it throws no exception, its value is the value of the try. If an exception is thrown, the codes are compared, in order, to each codei value, 1 through n. If one matches, the corresponding expri is run, and its value is returned. If none matches, and an exprd is present, it is run and its value returned. Otherwise, the original exception is re-thrown.
(define (oopsie x y)
    (try (/ (car x) y)
        (ERR_BADTYPE "Needs a list")
        (ERR_DIVZERO 1)
    )
)
lsp>(oopsie '(4 9 8) 2)
2
lsp>(oopsie '(5 9 1 2) 0)
1
lsp>(oopsie 4 9)
"Needs a list"
lsp>(oopsie '(2 1 9 8) (car 5))  
**** Error 5: car: Bad arg type. ****
   (oopsie (quote (2 1 9 8)) (car 5))
   (car 5)
Note that in the final case, the error is not caught by the try because it occurs when evaluating the parameters before oopsie's body or its try are started. It's also worth pointing out that try evaluates a single expression, not a list of expression as do succeeds and fails. You can use begin if you need to do more.

Generally, each codei will be just a single number, which is compared to the exception number. However, if the codei is a list, it is evaluated and treated as a boolean to decide if its expri should be returned. While being evaluated, the symbols ERROR and MESSAGE are bound to the code and message part, respectively, of the exception. So:

lsp>(try (/ 4 0) (ERR_DIVZERO 99))
99
lsp>(try (/ 4 0) ((= ERROR ERR_DIVZERO) 99))
99
Here, we've done the same thing twice, the easy way and the hard way.

If any expri's throw an exception, it cannot be caught by a later codej in the same try. That exception will be thrown by the try construct.

The fundamental operation for catching exceptions is called, unsurprisingly, catch. It is a bit unwieldy, and is used mainly as the primitive on which the above operations are based. Normal applications do not generally use it. That catch operation takes any number of expressions, and evaluates them from left to right. If one of these throws an exception, catch returns the pair of nil and the pair of the error code and the message from the error object. If not, catch returns the pair of #t and the value of the last expression evaluated.

lsp>(catch (/ 8 9) (/ 3 4) (/ 2 1) (/ 10 4))
(#t . 2)
lsp>(catch (/ 8 9) (/ 3 4) (/ 2 0) (/ 10 4))
(nil 10 . "Division by zero.")
lsp>(cdr (catch (/ 8 9) (/ 3 4) (/ 2 1) (/ 10 4)))
2
lsp>(cdr (catch (/ 8 9) (/ 3 4) (/ 2 0) (/ 10 4)))
(10 . "Division by zero.")
Once an expression throws an exception, no more are evaluated.