TIP #329: TRY/CATCH/FINALLY SYNTAX ==================================== Version: $Revision: 1.9 $ Author: Trevor Davel State: Final Type: Project Tcl-Version: 8.6 Vote: Done Created: Monday, 22 September 2008 URL: https://tip.tcl-lang.org329.html Discussions-To: http://wiki.tcl.tk/21608 Post-History: Obsoletes: TIP #89 ------------------------------------------------------------------------- ABSTRACT ========== This TIP proposes the addition of new core commands to improve the exception handling mechanism. It supercedes [TIP #89] by providing support for the error options dictionary introduced in Tcl 8.5 by [TIP #90]. RATIONALE =========== See [TIP #89] for general rationale for enhancing exception handling. The *try* syntax presented here is not intended to replace *catch*, but to simplify the expression of existing exception/error handling techniques, leading to greater code clarity and less error-prone workarounds for *finally* blocks. There is no deficiency in the functionality of Tcl's exception handling mechanisms - what is lacking is a more readable syntax and a standard for behaviour across packages for the common case of catching a subset errors or exceptions that are thrown from within a particular block of code. In Tcl 8.4 exceptions could be caught using *catch*, and exception information was available via the *catch* return value and resultvar. If the return value was TCL_ERROR (1) then the globals *::errorCode* and *::errorInfo* would be set according to the exception raised. [TIP #89] was written to work with this model, such that a catch handler (in a *try...catch*) would be able to capture the resultvar, errorCode and errorInfo. Tcl 8.5 implements [TIP #90] which extends *catch* to allow an additional dictionary of options (error information) to be captured. These options supercede the *::errorInfo* and *::errorCode* globals (though those are still supported for backward compatibility). It is therefore logical to extend/correct the syntax of [TIP #89] to support the options dictionary in preference to the older mechanism for capturing exception information. Benefits of adding this functionality to the core: * Bring to Tcl a construct commonly understood and widely used in other languages. * A standard for identifying categories/classes of errors, which will improve interoperability between packages. * A byte-coded implementation would be significantly faster than the Tcl implementation that is presented. SPECIFICATION =============== *try* /body/ ?/handler/ ...? ?*finally* /body/? *throw* /type message/ The *try* body is evaluated in the caller's scope. The handlers are searched in order of declaration until a matching one is found, and the associated body is executed. If no matching handler is found then *try* returns the result of the *try* body (exceptions will propagate up the stack as usual); otherwise *try* returns the result of the handler body (exceptions will propagate up the stack as usual). Only one handler body (that of the first matching handler) will be executed. If the handler body is the literal string "*-*" then the body for the subsequent handler will be used instead. It is an error for the last handler's body to be a literal "*-*". The *finally* body (if present) will be executed last, and is always executed whatever the results of the try and handler bodies (excepting resource exhaustion or cancellation). If the *finally* body returns an exceptional code then this will become the result of *try*, otherwise the result of the *finally* body is ignored. Since the *trap* handlers in the *try* control structure are filtered based on the exception's *-errorcode*, it makes sense to have a command that will encourage the use of error codes when throwing an exception. *throw* is merely a reordering of the arguments of the *error* command. /type/ is treated as a list by *trap* (see below), which maintains compatibility with the description of ::errorCode given in *tclvars*. HANDLERS ---------- Each handler is identified by a keyword. The fields following the keyword indicate what exceptions or errors are matched by the handler, the variables into which the result of the *try* body will be assigned (in the caller's scope), and the body of the handler. *on* /code {?resultVar ?optionsVar?} body/ The *on* handler allows exact matching against the exceptional return code (the integer value that would be returned by *catch*). The /code/ may be given as an integer or one of the magic keywords *ok* (0), *error* (1), *return* (2), *break* (3), *continue* (4). *trap* /pattern {?resultVar ?optionsVar?} body/ The *trap* handler allows list prefix matching against the *-errorcode* from the options when the exceptional return code is TCL_ERROR (1). Given a /pattern/ and an /errorcode/, a list prefix match is successful if for every element in /pattern/ there is a corresponding and identical element in /errorcode/. Trailing elements in /errorcode/ are ignored. Notes & clarifications: * Handlers are searched in order of declaration (left-to-right). One consequence of this search order is that an *on error* handler will supercede all subsequent *trap* handlers. * Any unhandled exception propagates. * The result of the last executed body (other than the *finally* body) is the result of the *try*. Exceptions in any /handler/ body or in the *finally* replace the existing exception and propagate. * If any exception is replaced (by an exception in a handler body or in the *finally* body) then the new exception shall introduce into its options dictionary the field *-during* that contains the options dict of the exception that was replaced. * If any errorcode happens to be not a list, a *trap* handler will be unable to process it. However, this should only happen in cases where there is a bug or other problem elsewhere, since *return* is documented to require the errorcode to be a list. EXAMPLES ========== Simple example of *try*//handler//*finally* logic in Tcl using currently available syntax: proc read_hex_file {fname} { set f [open $fname "r"] set data {} set code [catch { while { [gets $f line] >= 0 } { append data [binary format H* $line] } } em opts] if { $code != 0 } { dict set opts -code 1 set em "Could not process file '$fname': $em" } close $f return -options $opts $em } And the same example rewritten to use [*try*]: proc read_hex_file {fname} { set f [open $fname "r"] set data {} try { while { [gets $f line] >= 0 } { append data [binary format H* $line] } } trap {POSIX} {} { puts "POSIX-type error" } on error {em} { error "Could not process file '$fname': $em" } finally { close $f } } This illustrates how the intent of the code is more clearly expressed by [*try*]. REFERENCES ============ * Tcl 8.4 catch [] * Tcl 8.5 catch [] REJECTED ALTERNATIVES ======================= Various alternatives are discussed on the wiki [] along with reasons for their rejection. FUTURE EXTENSIONS =================== No specific future exceptions are planned, but *try* could be extended by adding new handler keywords and/or introducing new varnames to the variables list that is associated with each handler. It is recommended that new handlers maintain the established convention: *keyword* /criteria {?resultVar ?optionsVar?} body/ REFERENCE IMPLEMENTATION ========================== A reference implementation can be found at [] THANKS ======== Thanks in particular to DKF, NEM and JE for their feedback and suggestions on this TIP. COPYRIGHT =========== This document has been placed in the public domain. ------------------------------------------------------------------------- TIP AutoGenerator - written by Donal K. Fellows