This software adds exception handling to GNU Smalltalk. An exception is used to indicate an event (often erroneous) and specify some action in response to that event. For example, an implementation of a stack protocol may wish to raise an exception if it is empty when sent the message pop. The sender of the message may specify a block to execute if that exception is raised. An ExceptionEvent instance represents an exception. It simply consists of an optional data item used to help indentify it. Once created, an ExceptionEvent is immutable throughout its lifetime. ExceptionEvent class protocol for instance creation new Returns an anonymous ExceptionEvent new: name Returns an ExceptionEvent with name ExceptionEvent instance protocol for accessing exceptionName Returns the name the ExceptionEvent was created with (nil if none). ExceptionEvent instance protocol for raising an exception raise Raise the exception. raiseWith: someData Raise the exception and associate someData with it. These methods create an ExceptionValue object, which contains the ExceptionEvent raised and the data associated with it. st> exZero := ExceptionEvent new: 'zeroDivide' ! st> excValue := exZero raise ! st> excValue printNl ! an ExceptionValue of an ExceptionEvent named zeroDivide(nil) st> excValue := exZero raise ! st> (exZero raiseWith: 47) printNl ! an ExceptionValue of an ExceptionEvent named zeroDivide(47) ExceptionValue instance protocol for accessing event Answer the ExceptionEvent creating this object. data Answer someData used in raise with raiseWith: Obviously, simply creating an ExceptionValue isn't very useful by itself. Usually, you want to execute some code in response to the raised ExceptionEvent. This code is called an exception handler. An exception handler is associated with an ExceptionEvent instance. The exception handler is a block passed one argument: the ExceptionValue created by raising the ExceptionEvent. Exception handlers are specified inside an exception block. In addition to one or more exception handlers, an exception block consists of a do block that is invoked unconditionally. If this block sends raise or raiseWith: to an ExceptionEvent that has an exception handler associated with it, that exception handler is invoked. Exception blocks may be nested. For example, an inner exception block may contain an exception handler for an ExceptionEvent called 'empty', while an outer exception block has an exception handler for an ExceptionEvent called 'overflow'. An ExceptionEvent propogates outward from the exception block it was raised. In the example above, suppose the ExceptionEvent named 'overflow' is raised in the inner exception block. No exception handler in the inner block is invoked, since it doesn't have one containing handling that ExceptionEvent. The exception propogates outward, the opposite direction the do blocks are executed in. The outer exception block does have an exception handler associated with the raised ExceptionEvent, so that exception handler is invoked. Simply having an exception handler does not stop the propogation of an ExceptionEvent. This may be done inside the exception handler, it is called catching the event. If an exception handler does not catch the ExceptionEvent, its outward propogation continues. This enables nested exception handlers to be written. If all nested exception blocks fail to catch the ExceptionEvent, the block ceases executions and returns the ExceptionValue. Methods to catch the ExceptionEvent are discussed later. Now it is time to introduce the protocol. To shorten the description, { and } are used as meta-characters. It indicates that the message selectors inside are optional. ExceptionEvent class protocol for defining an exception block onAny: aHandlerBlock do: aBlock Invoke aBlock. If it raises any ExceptionEvent, invoke a handler block, passing to it the corresponding ExceptionValue as an argument. Answer the uncaught exception raised in aBlock, or nil if none. on: exceptionEvent1 handle: handler1 { on: exceptionEvent2 handle: handler2 } { on: exceptionEvent3 handle: handler3 } { on: exceptionEvent4 handle: handler4 } { onAnyOther: otherHandler } do: aBlock Invoke aBlock. If it raises exceptionEvent1, invoke handler1 with the created ExceptionValue as the only argument. If it raises exceptionEvent2, invoke handler2 (if that selector is present), and so on. exceptionEvent1, exceptionEvent2, exceptionEvent3, exceptionEvent4 must all be different ExceptionEvent instances. If any other exception is raised in the scope of aBlock, invoke otherHandler (if that selector is present. Answer the uncaught exception raised in aBlock, or nil if none. exceptionHandlers: aDictionary { onAnyOther: otherHandler } do: aBlock Invoke aBlock. aDictionary consists of ExceptionEvent instances as keys, exception handlers as values. Invoke the exception handler if aBlock raises an exception in aDictionary. Answer the uncaught exception raised in aBlock, or nil if none. ExceptionEvent instance methods for defining an exception block handle: aHandlerBlock do: aBlock Invoke aBlock. If it raises the instance's ExceptionEvent invoke aHandlerBlock. Answer the uncaught exception raised in aBlock, or nil if none. In order to stop the propogation of the ExceptionEvent, it must be caught. The ExceptionValue instance passed to the handler block is used to catch the exception. The methods provided are: ExceptionValue instance methods for catching the exception terminate Immediately stop executing the exception block this exception handler belongs to. Note this may be outside the exception block the exception value was raised in, or it may be same one. resume When the exception handler completes, return to the do block the exception was raised in. The do block procedes from the point where the exception was raised. Here is an example. Smalltalk at: #x put: nil ! Smalltalk at: #empty put: (ExceptionEvent new: 'empty') ! Smalltalk at: #anonymousException put: (ExceptionEvent new) ! Smalltalk at: #miscException put: (ExceptionEvent new: 'misc') ! x := empty handle: [ :ev | 'in outer empty exception block. Exception = ' printNl. ev printNl. ev resume ] do: [ '* entering outer do block' printNl. x := ExceptionEvent on: anonymousException handle: [ :ev | 'in anonymousException exception inner block. Exception = ' printNl. ev printNl. ev terminate. 'I will never be executed' printNl ] on: empty handle: [ :ev | 'in empty inner exception block. Exception = ' printNl. ev printNl ] onAnyOther: [ :ev | 'in other block. Exception = ' printNl. ev printNl. ev resume ] do: [ '** entering inner do block' printNl. miscException raiseWith: 'mystery exception'. empty raise. anonymousException raiseWith: 'my exception data'. '** ending inner do block' printNl ]. '** inner do block answered ' print. x printNl. empty raiseWith: 345. miscException raise. '* exiting outer do block' printNl ] ! '* outer do block answered ' print ! x printNl ! Its execution produces: * entering outer do block ** entering inner do block in other block. Exception = an ExceptionValue of an ExceptionEvent named misc(mystery exception) in empty inner exception block. Exception = an ExceptionValue of an ExceptionEvent named empty(nil) in outer empty exception block. Exception = an ExceptionValue of an ExceptionEvent named empty(nil) in anonymousException exception inner block. Exception = an ExceptionValue of an ExceptionEvent(my exception data) ** inner do block answered nil in outer empty exception block. Exception = an ExceptionValue of an ExceptionEvent named empty(345) * outer do block answered an ExceptionValue of an ExceptionEvent named misc(nil) First, the entering messages are printed. The following results explain the results of the raised exceptions. miscException raiseWith: 'mystery exception'. No exception handler for miscException is specified in the inner exception block. So the exception handler specified by the onAnyOther: selector is invoked. It catches the exception, sending a resume message. This causes control to return to the point the exception was raised. empty raise. The inner exception block has a handler for empty, so it is invoked. This handler doesn't catch the exception, so it propogates to the outer exception block. This invokes the outer exception handler for empty. It catches uses the resume message to catch the exception, sending execution back to the inner block, where the exception was raised. Had the outer exception block not caught the exception, both exception blocks would have immediately been terminated. anonymousException raiseWith: 'my exception data'. The inner exception block does have a handler for this exception, so it is invoked. This catches the exception with the terminate message. This immediately aborts the inner exception block, so the print after the terminate and the print after the exception was raised aren't executed. The inner block was terminated because the terminate message was sent in that block, NOT because the exception was raised in that block. Since terminate does catch the exception, nil is returned. Control now procedes to the outer exception block. It raises these exceptions. empty raiseWith: 345. This invokes the outer exception handler correspoding to the empty Exception event. Note that the inner empty exception handler is not invoked, because it is not in the scope of the exception handler. Also note the handle:do: message sent to empty defines this block. miscException raise. Since neither this exception block, nor any enclosing exception block (in this case there aren't any) have a handler for this exception, no exception handler is invoked. This exception is uncaught, causing immediate termination of the exception block and answering the uncaught ExceptionValue.