Copyright 1984 by Gary M. Rader July 15, 1984 ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍ» º Error-handling in Arithmetic Expressions º º º º by º º º º Gary M. Rader º ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍͼ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Introduction ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ In the previous issue I described how to convert an expression from infix form to prefix form so that LISP could evaluate it. (Such a conversion is useful when, for example, we are designing a spreadsheet where user-defined formulas are evaluated.) One area not covered was that of error-handling. How are errors such as division by zero, or multiplication by a string instead of a number, handled? Traditionally, spreadsheets have returned the string *ERROR* (or some form thereof) when errors like division by zero occur, and *NA* (for Not Available) for errors like multiplication by an undefined cell. An obvious way to handle such problems is to make every function check the input operand values before carrying out its operation. If any operand is unsatisfactory, a value, such as *ERROR*, is returned as the function value. This method is tedious and boring, possibly requiring redundant code, which must be added to each function. Luckily, muLISP permits us to solve this problem in a much more elegant fashion by using its' error-handling functions. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Error-handling Functions ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ muLISP has two error-handling functions to assist us: NONNUMERIC and ZERODIVIDE. When a non-numeric operand occurs in an arithmetic expression, the function NONNUMERIC is called. Similarly, when 0 occurs as a divisor, ZERODIVIDE is called. Initially, these functions are defined to display an identifying message and then to call muLISP's BREAK routine at which point the user can stop (to examine the environment and find the cause of the break), continue, or exit to the operating system. However, like any muLISP function, NONNUMERIC and ZERODIVIDE can be redefined to behave as we want when those errors arise. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ ZERODIVIDE ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ We examine the ZERODIVIDE case first, as it is simpler than the NONNUMERIC case. When division by zero occurs, we want the entire evaluation process to stop and the value *ERROR* to be returned. Notice that even if the immediate expression is deeply imbedded within other expressions, we want the evaluation of all expressions to return the value *ERROR*. If the expression being evaluated is (3.14 * @SUM(A+B/2, B*C, 24*(HOURS/D+A)) + 14) and D+A is 0, we want evaluation of the expression to terminate immediately with the value *ERROR*. The function IToP, defined in the previous issue, converts an expression from infix to prefix notation. If Exp is an infix expression, then (IToP Exp) converts it to prefix notation and (EVAL (IToP Exp)) both converts and evaluates it, returning the value of the expression. If we imbed this conversion and evaluation process inside a suitable CATCH, we can get what we want. That is, let us redefine ZERODIVIDE to execute a THROW with the label TopLevel and the value *ERROR*: (DEFUN ZERODIVIDE (LAMBDA (Function Operands) (THROW 'TopLevel '*ERROR*))) Then the following expression yields the desired result: (CATCH 'TopLevel (EVAL (IToP Exp))) Now, whenever a zero divisor appears, the EVAL is terminated and control is "thrown" to the corresponding CATCH with the value *ERROR*. If no zero divisor occurs, the THROW is never executed and the value of (EVAL (IToP Exp)) is returned as before. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ NONNUMERIC ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ We define NONNUMERIC in a similar fashion. However, as a function may have many operands, we may have to check several operands before finding an improper one. Let us assume we want the following behavior: 1. If the argument is the empty string (i.e., "" - an undefined cell) or the string *NA*, return not available (*NA*). 2. If the argument is a string other than *ERROR* (i.e., a label), replace it with zero. 3. Otherwise return error (*ERROR*). (DEFUN NONNUMERIC (LAMBDA (Function Operands % Local: % l1 l2) (SETQ l1 (APPEND Operands)) ; Copy list of Operands. (SETQ l2 l1) (LOOP ((NULL l1)) ; All operands checked ; --- exit loop. ((NOT (NUMBERP (CAR l1))) ; Found bad operand. ((OR (EQ (CAR l1) "") ; Undefined cell or (EQ (CAR l1) '*NA*)) ; already not avail --- (THROW 'TopLevel '*NA*)) ; exit with *NA*. ((EQ (CAR l1) '*ERROR*) ; Already bad --- exit (THROW 'TopLevel '*ERROR*)) ; with *ERROR*. ((AND (ATOM (CAR l1)) ; Label --- replace (NOT (EQ (CAR l) '*ERROR*))) (RPLACA l1 0)) ; with 0. (THROW 'TopLevel '*ERROR*)) ; Really bad --- exit ; with *ERROR*. (POP l1)) (EVAL (CONS Function l2)))) ; Recreate expression & ; evaluate it. First, we make a copy of the operands (using APPEND), and if a label is encountered it is replaced (via RPLACA) in the copy with 0. This insures that we don't inadvertently change an operand in the "outside world," as LISP passes a pointer to the list of operands to NONNUMERIC, not a copy of the operands. Also notice that we evaluate the modified expression after finding the first label. If several labels exist in the expression, they will be caught and modified by NONNUMERIC in successive re-evaluations. We also specifically check for the case of a cell having the value *NA* or *ERROR*. The technique used in NONNUMERIC can be extended to see if a non-numeric operand is actually a cell designator, such as B24, or designates a range of cells. In the first case, the non-numeric operand can be replaced by the value in cell B24, and in the second case, the operands can be replaced by the values of these cells. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Examples ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ (CATCH 'TopLevel (EVAL (IToP '(10 + 9)))) 19 (CATCH 'TopLevel (EVAL (IToP '(1 + 2 / 0)))) *ERROR* (CATCH 'TopLevel (EVAL (IToP '(40 * (7 + ""))))) *NA* (CATCH 'TopLevel (EVAL (IToP '(40 * (7 + January))))) 280 (CATCH 'TopLevel (EVAL (IToP '(1 + *ERROR* / 0)))) *ERROR* ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ Conclusion ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ muLISP's error-handling functions provide a very powerful and elegant method to tailor a program's response to an arithmetic error when evaluating functions. This technique is transparent to all functions, and new functions can be added to the system without modifying their behavior to fit certain patterns, as the change in behavior is automatic. ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ The LISP Used in this Article ³ ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´ ³ muLISP-83 ³ ³ $250 ³ ³ The Soft Warehouse ³ ³ P.O. Box 11174 ³ ³ Honolulu, Hawaii 96828-0174 ³ ³ (808)734-5801 ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿ ³ File Name: ÛÛ lisp1.txt ÛÛ ³ ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ