my_question
my_question

Reputation: 3245

Why can't I access errorInfo and errorCode

I have the following code:

$ cat ~/tmp/2.tcl
set zero 0
proc p1 {} {
    if {[catch {expr 1/$zero} err]} {
        puts "errorCode=$errorCode"
        puts "errorInfo=$errorInfo"
    }
}

p1

When I source it, I get error accessing errorCode:

$ tclsh ~/tmp/2.tcl
can't read "errorCode": no such variable
    while executing
"puts "errorCode=$errorCode""
    (procedure "p1" line 3)
    invoked from within
"p1"
    (file "~/tmp/2.tcl" line 9)

I tried changing to $::errorCode, but did not help.

Can you see what is wrong?

Upvotes: 0

Views: 1138

Answers (4)

Paul Sherman
Paul Sherman

Reputation: 76

The options dictionary from the result of a [catch ...] seem to contain various elements in a curious pattern depending on the -code and -level of what threw the exception inside the [catch]. A few examples follow:

rc=0 catch.rslt=XXX catch.opts=-code 0 -level 0
rc=1 catch.rslt=XXX catch.opts=-code 1 -level 0 -errorinfo {A B C foo rvdebug.cfg 223} -errorcode {1 2 C}
rc=2 catch.rslt=XXX catch.opts=-code 0 -level 0
rc=3 catch.rslt=XXX catch.opts=-code 3 -level 0
rc=4 catch.rslt=XXX catch.opts=-code 4 -level 0
rc=0 catch.rslt=XXX catch.opts=-code 0 -level 0
rc=1 catch.rslt=XXX catch.opts=-code 1 -level 0 -errorinfo {A B C} -errorcode {1 2 C}
rc=2 catch.rslt=XXX catch.opts=-code 0 -level 0
rc=3 catch.rslt=XXX catch.opts=-code 3 -level 0
rc=4 catch.rslt=XXX catch.opts=-code 4 -level 0
rc=2 catch.rslt=XXX catch.opts=-code 0 -level 1
rc=2 catch.rslt=XXX catch.opts=-code 1 -level 1
rc=2 catch.rslt=XXX catch.opts=-code 2 -level 1
rc=2 catch.rslt=XXX catch.opts=-code 3 -level 1
rc=2 catch.rslt=XXX catch.opts=-code 4 -level 1

For example, a throwable procedure ...

proc foo {c lvl} {
  return -code $c \
         -errorinfo [list A B C] \
         -errorcode [list 1 2 C] \
         -level $lvl XXX \
}

... is called by a catchable handler ...

set rc [catch {foo 0 0} rslt opts]

... and its results examined as

echo "rc=$rc catch.rslt=$rslt catch.opts=$opts"

To summarize,

  • return allows -errorcode only when -code is 1 (TCL_ERROR) and -level is 0 (TRACE_TOP), otherwise syntax error
  • catch opts always contain the two elements -code and -level
  • catch opts has -errorinfo suffixed with func,file,line only when -code is 1 (TCL_ERROR) and -level is 0 (TRACE_TOP)
  • catch opts has -errorinfo un-suffixed only when -code is 1 (TCL_ERROR) and -level is 1 (TRACE_SUBTOP)
  • catch opts sets -code as 0 only when -code is 2 (TCL_RETURN) and -level is 0 or 1 (TRACE_TOP or TRACE_SUBTOP)
  • catch always returns 2 (TCL_RETURN) when -level is more than 1 (below TRACE_SUBTOP)
  • catch returns -code when -level is 0 or 1 (TRACE_TOP or TRACE_SUBTOP), and is not set to 0 at -code 2 (TCL_RETURN)
  • catch returns rslt unless proc is called with or has "wrong # args", a message returned instead along with -code set to 1 (TCL_ERROR)

where

# -code TCL_OK | TCL_ERROR | TCL_RETURN | TCL_BREAK | TCL_CONTINUE
# -level trace-depth-of-call-stack  -- non-negative number
# -errorinfo [list ... ... ...]     -- trio of elements meaning, e.g., proc, file, linenum
# -errorcode [list ...]             -- arbitrary information, alphanumeric, one or more
# -errorinfo in catch opts only when -code is 1 (TCL_ERROR) and -level is 0 (TRACE_TOP) or 1 (TRACE_SUBTOP), value is dflt-trace unless -errorinfo specified  --> ::errorInfo
# -errorcode in catch opts only when -code is 1 (TCL_ERROR) and -level is 0 (TRACE_TOP) or 1 (TRACE_SUBTOP), value is NONE unless -errorcode specified        --> ::errorCode

and

# TCL Return Codes: TCL_OK 0 TCL_ERROR 1 TCL_RETURN 2 TCL_BREAK 3 TCL_CONTINUE 4
# TCL Trace Levels: TRACE_TOP 0 TRACE_SUBTOP 1 ... ...

In my particular case of OpenOCD presumably a simpler and/or older version of jim-tcl is used, where the return allows only four arguments:

# return ?-code code? ?-errorinfo stacktrace? ?-level level? ?result?

Thus, neither -errorstack [list ...] nor -options [dict create ... ...] are supported for any -code or -level.


The MWE which created this output is shown below.

proc foo {c lvl} { return -code $c -errorinfo [list A B C] -errorcode [list 1 2 C] -level $lvl XXX} set rc [catch {foo 0 0} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 1 0} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 2 0} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 3 0} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 4 0} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts"

set rc [catch {foo 0 1} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 1 1} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 2 1} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 3 1} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 4 1} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts"

set rc [catch {foo 0 2} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 1 2} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 2 2} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 3 2} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts" set rc [catch {foo 4 2} rslt opts] ; echo "rc=$rc catch.rslt=$rslt catch.opts=$opts"

Upvotes: 0

Paul Sherman
Paul Sherman

Reputation: 76

Sergei's answer is very good. Using the dictionary returned by the second optional parameter of catch [...] is spot on. However, I find slightly different entries in this dictionary and -errorinfo is not one of them.

There are only two dictionary entries in opts. What works for me is

puts "errorCode=[dict get $opts -code]"
puts "errorLevel=[dict get $opts -level]"

Perhaps I have an older or more specialized version of Tcl/Tk?

Upvotes: 0

Sergei Golovan
Sergei Golovan

Reputation: 611

Starting from Tcl 8.5 [catch] doesn't set the errorCode and errorInfo global variables. (As Donal has pointed out, it still does, so they can be accessed as $::errorCode and $::errorInfo). And in addition it puts their values into a dictionary which name is to be specified as the third argument. The following code

#!/usr/bin/tclsh

set zero 0
proc p1 {} {
    if {[catch {expr 1/$zero} err opts] == 1} {
        puts "errorCode=[dict get $opts -errorcode]"
        puts "errorInfo=[dict get $opts -errorinfo]"
    }
}

p1

prints

errorCode=NONE
errorInfo=can't read "zero": no such variable
    while executing
"expr 1/$zero"

in Tcl 8.5.19, and

errorCode=TCL READ VARNAME
errorInfo=can't read "zero": no such variable
    while executing
"expr 1/$zero"

in Tcl8.6.6.

You'd probably want to use $::zero in the division after which the result would be

errorCode=ARITH DIVZERO {divide by zero}
errorInfo=divide by zero
    while executing
"expr 1/$::zero"

Upvotes: 4

Donal Fellows
Donal Fellows

Reputation: 137767

The errorInfo and errorCode variables are globals. You should either use the global command to bring them into scope or use their fully-qualified names (i.e., precede with ::).

It might be easier to pick the information out of the result options dictionary (a new feature in 8.5).

Upvotes: 3

Related Questions