9.6.1.2275 THROW EXCEPTION
If any bits of n are non-zero, pop the topmost exception frame from the exception stack, along with everything on the return stack above that frame. Then restore the input source specification in use before the corresponding CATCH and adjust the depths of all stacks defined by this standard so that they are the same as the depths saved in the exception frame (i is the same number as the i in the input arguments to the corresponding CATCH), put n on top of the data stack, and transfer control to a point just after the CATCH that pushed that exception frame.
If the top of the stack is non zero and there is no exception frame on the exception stack, the behavior is as follows:
If n is minus-one (-1), perform the function of 6.1.0670 ABORT (the version of ABORT in the Core word set), displaying no message.
If n is minus-two, perform the function of 6.1.0680 ABORT" (the version of ABORT" in the Core word set), displaying the characters ccc associated with the ABORT" that generated the THROW.
Otherwise, the system may display an implementation-dependent message giving information about the condition associated with the THROW code n. Subsequently, the system shall perform the function of 6.1.0670 ABORT (the version of ABORT in the Core word set).
See:
Rationale:
Typical use:
KEY DUP [CHAR] Q = IF 1 THROW THEN ;
: do-it ( a b -- c) 2DROP could-fail ;
: try-it ( --)
1 2 ['] do-it CATCH IF
( x1 x2 ) 2DROP
." There was an exception" CR
ELSE
." The character was " EMIT CR
THEN
;
; retry-it ( -- )
BEGIN 1 2 ['] do-it CATCH
WHILE
( x1 x2) 2DROP
." Exception, keep trying" CR
REPEAT ( char )
." The character was " EMIT CR
;
Implementation:
?DUP IF ( exc# ) \ 0 THROW is no-op
HANDLER @ RP! ( exc# ) \ restore prev return stack
R> HANDLER ! ( exc# ) \ restore prev handler
R> SWAP >R ( saved-sp ) \ exc# on return stack
SP! DROP R> ( exc# ) \ restore stack
\ Return to the caller of CATCH because return
\ stack is restored to the state that existed
\ when CATCH began execution
THEN
;
Testing:
: t1 9 ;
: c1 1 2 3 ['] t1 CATCH ;
T{ c1 -> 1 2 3 9 0 }T \ No THROW executed
: t2 8 0 THROW ;
: c2 1 2 ['] t2 CATCH ;
T{ c2 -> 1 2 8 0 }T \ 0 THROW does nothing
: t3 7 8 9 99 THROW ;
: c3 1 2 ['] t3 CATCH ;
T{ c3 -> 1 2 99 }T \ Restores stack to CATCH depth
: t4 1- DUP 0> IF RECURSE ELSE 999 THROW -222 THEN ;
: c4 3 4 5 10 ['] t4 CATCH -111 ;
T{ c4 -> 3 4 5 0 999 -111 }T \ Test return stack unwinding
: t5 2DROP 2DROP 9999 THROW ;
: c5 1 2 3 4 ['] t5 CATCH \ Test depth restored correctly
DEPTH >R DROP 2DROP 2DROP R> ; \ after stack has been emptied
T{ c5 -> 5 }T
ContributeContributions
AlexDyachenko [32] The value of STATE should be restoredProposal2017-09-03 11:07:49
AndrewRead [36] EXCEPTION LOCALsProposal2017-10-28 07:04:49
MitraArdron [170] THROW: text doesn't match implementation exampleComment2020-12-06 08:05:30
I think the text needs updating - it is not clear what happens when the value is zero, though the implementation example is.
I think this is particularly imporant as this differs from other implementations of THROW, for example in eForth where it is always triggered , i.e. there is no "?DUP IF"
JimPeterson [191] Throwing past DO/LOOPComment2021-04-19 19:06:28
I didn't implement my DO
/LOOP
to store limits/counters on the return stack (limited system, no room there, the standard does not require it). If I were to THROW
up through a DO
/LOOP
(particularly a nested one), using the above specification for THROW
as my bare-bones attempt at implementing it, I know that "an ambiguous condition" would then most certainly exist.
This is my own fault, but shouldn't there at least be a note in the standard along the lines of "The system should implement an appropriate number of UNROLL
operations when throwing an exception up through DO
/LOOP
constructs."? For most systems, this is implied by adjusting the return stack, and systems implementors would just think "easy", but at least it would be explicitly stated.
Certainly, any similar user-made constructs have identical issues that are entirely the user's responsibility. Maybe this is an argument for making CATCH
/THROW
compatible with DEFER!
? How else would user code that acquires global resources properly interoperate in the presence of THROW
?
ruv [283] Input source after THROWComment2023-02-15 22:50:52
In presence of the save-input
and restore-input
words, it could appear that throw
should restore the state of the input source as it was before execution of the corresponding catch
, due to similar wording.
But actually the specification only requires that if a throw code is non zero, the input source is the same before and after execution of catch
. But the state of this input source may be changed.
Namely, the specification for THROW
says:
- (if n <> 0) "restore the input source specification in use before the corresponding CATCH" (emphasis added)
While the specification for RESTORE-INPUT
says:
- "restore the input source specification to the state described by..." (emphasis added)
If the state of the input source were to be restored, the following test would show the same line "source: test" twice:
: test [: cr ." source: " source type cr ." next line: " refill cr . cr abort ;] catch drop ." source: " source type cr postpone \ ;
test
\ some other line
NB: the state of the input source can be only changed via >in
(directly or indirectly), refill
, and restore-input
. No other way to change this state for a standard program.
Please correct me if I'm wrong somewhere. Otherwise, the wording should reflect this nuance more explicit, I think.
MitraArdron [304] Imlementation seems wrong for the "-2" caseSuggested reference implementation2023-08-03 10:30:03
If the top of the stack is -2 , and there is no handler, then THROW is supposed to display a message
I can't see how the reference implementation does that. It deals with the case of being called from inside a CATCH correctly as far as I can tell, but does not handle the case of HANDLER being empty - in fact if HANDLER=0 I think this would crash.
Note - I picked this up when the Hayes test suite failed its exception test set.