’From Smalltalk 5.5k XM November 24 on 22 November 1980 at 2:57:08 am.’
"ExceptionHandler"
Class new title: ’ExceptionHandler’
    subclassof: Object
    fields: ’
        fDoContext
        fCode
        fTrapCode
        fTrapCondition
        fResult
        fPostTrapIndicator
        fStatusIndicator
    
    declare: ’’;
    asFollows

EXCEPTION HANDLER J.C. Althoff 7/79

DOCUMENTATION
description
[
"

This class provides some exception handling capabilities that can be used in a variety of applications. It is intended to be used in the following way:

1. An ExceptionHandler object is created to supervise the execution of a block of statements.
    EH ← ExceptionHandler new.
2. Each object that is capable of detecting an exception is informed of the existence of the ExceptionHandler object.
    dpj exceptionHandler: EH.
3. The ExceptionHandler object is then sent the message
’do⦂ pCode onTrapDo⦂ pTrapCode’. The parameter pCode is a block of statements that is to be supervised by the ExceptionHandler object. The parameter pTrapCode is a block of statements that is to be executed whenever an exception is detected.
    EH do⦂ [ ... ] onTrapDo⦂ [ ... ].
4. When an exception is detected during the execution of one of the statements in
pCode the message ’trap’ can be sent to the ExceptionHandler object which will then take control. If information about the exception is available, it can be relayed by sending the message ’trap:’ with the information specified as the parameter.
    EH trap. (or) EH trap: information.
5. When the ExceptionHandler object gets control it saves the parameter if one was sent, then executes
pTrapCode. The saved parameter can be retrieved by sending the message ’trapCondition’. The statements in pTrapCode should take any actions necessary to handle the exception. Messages can be included in pTrapCode that tell the ExceptionHandler object what to do when pTrapCode completes. The available messages are:
    5.1 ’restart’ which restarts the execution of pCode,
    5.2 ’continue’ which continues pCode at the point at which ’trap’ was sent,
    5.3 ’abort’ which aborts the execution of pCode and returns control to the statement following ’do⦂ pCode onTrapDo⦂ pTrapCode’.
    EH restart. (or) EH continue. (or) EH abort.
6. The message
’do⦂onTrapDo⦂’ returns a result which can be specified during the execution of either pCode or pTrapCode by the message ’result ← pResult’. This result value can be retrieved anytime after it has been specified by sending the message ’result’.
    EH result ← true.
    [ EH result ⇒ [ ... ] ].
7. If
pCode is terminated abnormally from a user notify window, the message ’release’ should be sent to return the ExceptionHandler object to its initial state.
    EH release.
8. Special error checking is included in the ExceptionHandler object to detect calls to
pTrapCode during the execution of pTrapCode and other invalid conditions.
"
]
implementationNotes
[
"
FIELDS
fDoContext : a Context that controls the execution of the message ’do⦂onTrapDo⦂’.
fCode : a Context that controls the execution of the statements passed as the first parameter to ’do⦂onTrapDo⦂’.
fTrapCode : a Context that controls the execution of the statements passed as the second parameter to ’do⦂onTrapDo⦂’.
fTrapCondition : the object passed as the parameter to ’trap:’.
fResult : the object that is the result of ’do⦂onTrapDo⦂’.
fPostTrapIndicator : an Integer specifying the action to be taken after fTrapCode is executed.
fStatusIndicator : an Integer specifying the status of ’do⦂onTrapDo⦂’; nil means ’do⦂onTrapDo⦂’ has not been invoked, 1 means fCode is active, 0 means fTrapCode is active.
"
]

CONTROL STRUCTURE
do⦂ pCode onTrapDo⦂ pTrapCode
"...executes the set of statements in the block pCode and, conditionally, the set of statements in the block pTrapCode. If ’trap’ or ’trap:’ is invoked during the execution of pCode, the execution is interrupted and pTrapCode is started. When pTrapCode completes, pCode is either continued, restarted, or aborted, depending on which of ’continue’, ’restart’, and ’abort’ is sent during the execution of pTrapCode. A notify window is created if ’trap’ or ’trap:’ is sent when pCode is not active, or if ’do⦂onTrapDo⦂’ is sent again before the first invocation completes."
[
    [ fStatusIndicator ≠ nil ⇒ [ user notify: ’ExceptionHandler is active’. ] "1"
    ].
    fStatusIndicator ← 1. "2"
    fCode ← pCode. "3"
    fTrapCode ← pTrapCode. "4"
    fDoContext ← thisContext. "5"
    fTrapCondition ← nil. "6"
    fResult ← true. "7"
    fCode eval. "8"
    self release. "9"
    ⇑ fResult "10"
]
"
1. If fStatusIndicator ≠ nil, then the previous invocation of ’do⦂onTrapDo⦂’ has not completed properly. Create a notify window in this case.
2. Set fStatusIndicator to indicate pCode is active.
3. Save pCode for execution and restart.
4. Save pTrapCode for later execution.
5. Save the context of ’do⦂onTrapDo⦂’ for ’restart’ and ’abort’.
6. Initialize the trap condition to nil.
7. Initialize the result of ’do⦂onTrapDo⦂ ’ to true.
8. Execute pCode.
9. Set fields to nil to release saved contexts.
10. Return fResult (which may have been modified during the execution of pCode or pTrapCode).
"

TRAP HANDLER
trap
"...interrupts the execution of pCode and begins pTrapCode (from ’do⦂ pCode onTrapDo⦂ pTrapCode’)."
[
    self trap: nil.
]
trap: pTrapCondition
"...interrupts the execution of pCode and begins pTrapCode (from ’do⦂ pCode onTrapDo⦂ pTrapCode’). The trap condition is set to pTrapCondition."
    |tTrapCode i
[
    [ fStatusIndicator
        ≡ nil ⇒ [ user notify: ’ExceptionHandler is not active’ ]; "1"
        = 0 ⇒ [ user notify: ’ExceptionHandler trap code is already active’ ] "2"
    ].
    fStatusIndicator ← 0. "3"
    fTrapCondition ← pTrapCondition. "4"
    fPostTrapIndicator ← 0. "5"
    tTrapCode ← fTrapCode cleancopy. "6"
    tTrapCode eval. "7"
    for⦂ i to: tTrapCode totalPT do⦂
        [ fTrapCode setPT: i to: (tTrapCode getPT: i). ]. "8"
    fStatusIndicator ← 1. "9"
    [ fPostTrapIndicator "10"
        = 0 ⇒ [ self restartCode. ];
        = 1 ⇒ [ self abortCode. ];
        = 2 ⇒ [ ]
    ].
]
"
1. If fStatusIndicator ≡ nil, then ’do⦂onTrapDo⦂’ has not been invoked and there is no trap code to execute.
2. if fStatusIndicator = 0, then fTrapCode is active. This is an invalid state.
3. Set fStatusIndicator to indicate fTrapCode is active.
4. Set the trap condition to pTrapCondition.
5. Set the default post trap action to ’restart’.
6. Make a new copy of the context, fTrapCode.
7. Execute the new copy of fTrapCode.
8. Set the parameters and temporaries in the context, fTrapCode to those of the executed copy. This is done so that fCode will see any changes made to these variables by fTrapCode.
9. Set fStatusIndicator to indicate fTrapCode is not active.
10. Select the terminating action from the value of fPostTrapIndicator.
"

POST TRAP SPECIFICATION
abort
"...specifies that the code interrupted by ’trap’ or ’trap:’ will be aborted when the trap code completes. Control will return to the statement that follows the invocation of ’do⦂onTrapDo⦂’."
[
    fPostTrapIndicator ← 1.
]
continue
"...specifies that the code interrupted by ’trap’ or ’trap:’ will be continued when the trap code completes."
[
    fPostTrapIndicator ← 2.
]
restart
"...specifies that the code interrupted by ’trap’ or ’trap:’ will be restarted when the trap code completes."
[
    fPostTrapIndicator ← 0.
]

MISC
result
"...returns the result set by ’result←’."
[
    ⇑ fResult
]
result ← pResult
"...sets the result value of ’do⦂onTrapDo⦂’ to pResult."
[
    fResult ← pResult.
]
trapCondition
"...returns the condition that was passed as the parameter to ’trap:’."
[
    ⇑ fTrapCondition
]

POST TRAP ACTION (internal)
abortCode
"...releases the context chain and returns fResult to the caller of ’do⦂onTrapDo⦂’."
    |tDoContextCaller
[
    tDoContextCaller ← fDoContext caller. "1"
    thisContext caller releaseTo: tDoContextCaller. "2"
    self release. "3"
    tDoContextCaller push: fResult. "4"
    Top run: tDoContextCaller at: Top currentPriority. "5"
]
"
1. Get the caller of ’do⦂onTrapDo⦂’.
2. Release the context chain from the caller of thisContext up to the caller of ’do⦂onTrapDo⦂’.
3. Set fields to nil to clean up pointers to unwanted contexts.
4. Return fResult to the caller of ’do⦂onTrapDo⦂’.
5. Run the caller of ’do⦂onTrapDo⦂’.
"
release
"...resets the exception handler to its initial condition."
[
    fCode ← nil.
    fTrapCode ← nil.
    fDoContext ← nil.
    fStatusIndicator ← nil.
]
restartCode
"...releases the context chain and restarts the block of statements passed as the first parameter to ’do⦂onTrapDo⦂’."
[
    thisContext caller releaseTo: fCode. "1"
    fCode restart. "2"
    fCode push: fDoContext. "3"
    Top run: fCode at: Top currentPriority. "4"
]
"
1. Release the context chain from the caller of thisContext up to fCode.
2. Resets the pc and stack pointer of fCode.
3. Push the return context of fCode onto its tempframe.
4. Run fCode.
"

TEST AND DIAGNOSTIC
test "ExceptionHandler new test."
    | guard i
[
    guard ← ExceptionHandler new. user clear. i ← 0.
    guard do⦂
        [ user show: i asString. i ← i+1. guard trap: i. user show: ’ end’. ]
    onTrapDo⦂
        [ i < 10 ⇒ [ guard restart ] guard continue ].
    user show: ’ done.’. user cr. ⇑ guard result
]
testTransaction "ExceptionHandler new testTransaction."
    | e i max f
[
    e ← ExceptionHandler new. i ← 0. max ← 5. dpj exceptionHandler: e.
    dpj open. f ← dpj file: ’test.test’.
    e do⦂ [f reset. until⦂ [ f end ] do⦂ [ user show: (f next) inString. ]. ]
    onTrapDo⦂
        [ user cr; show: ’Juniper not responding’.
        f release. dpj release. i ← i+1. [ i < max ⇒ [ e restart. ] e abort ]. ].
    user cr; show: ’done’. f close. dpj close.
]

EXAMPLES
juniperTest "ExceptionHandler new juniperTest."
    | e i max f
[
    e ← ExceptionHandler new. i ← 0. max ← 5. dpj exceptionHandler: e.
    dpj open. f ← dpj file: ’test.test’.
    e do⦂ [f reset. until⦂ [ f end ] do⦂ [ user show: (f next) inString. ]. ]
    onTrapDo⦂
        [ user cr; show: ’Juniper not responding’.
        f release. dpj release. i ← i+1. [ i < max ⇒ [ e restart. ] e abort ]. ].
    user cr; show: ’done’. f close. dpj close.
]
loopTest "ExceptionHandler new loopTest."
    | guard i
[
    guard ← ExceptionHandler new. user clear. i ← 0.
    guard do⦂
        [ user show: i asString. i ← i+1. guard trap: i. user show: ’ end’. ]
    onTrapDo⦂
        [ i < 10 ⇒ [ guard restart ] guard continue ].
    user show: ’ done.’. user cr. ⇑ guard result
]

SystemOrganization classify: ↪ExceptionHandler under: ’ExceptionHandling’.