I am trying to understand errors and exceptions in Tcl. I have written a small code as follows
proc Div3 {a b} {
return [Div2 $a $b]
}
proc Div2 {a b} {
return [Div $a $b]
}
proc Div {a b} {
if {$b == 0} {
error "Error generated by error" "Info String for error" 401
} else {
return [expr $a/$b]
}
}
if {[catch {puts "Result = [Div3 10 0]"} errmsg]} {
puts "ErrorMsg: $errmsg"
puts "ErrorCode: $errorCode"
puts "ErrorInfo:\n$errorInfo\n"
}
when I run this using tclsh.exe, the debugger output is shown as follows,
% tclsh error-file-1.tcl
ErrorMsg: Error generated by error
ErrorCode: 401
ErrorInfo:
Info String for error
(procedure "Div" line 1)
invoked from within
"Div $a $b"
(procedure "Div2" line 2)
invoked from within
"Div2 $a $b"
(procedure "Div3" line 2)
invoked from within
"Div3 10 0"
However, when I run the same using tclsh.exe via Komodo IDE, I am getting the debugger output as follows
ErrorMsg: Error generated by error
ErrorCode: 401
ErrorInfo:
Info String for error
invoked from within
"DbgNub_uplevelCmd 1 $cmd"
invoked from within
"Div $a $b"
invoked from within
"DbgNub_uplevelCmd 1 $cmd"
invoked from within
"Div2 $a $b"
invoked from within
"DbgNub_uplevelCmd 1 $cmd"
invoked from within
"Div3 10 0"
invoked from within
"DbgNub_uplevelCmd 1 $cmd"
invoked from within
"DbgNub_Do 0 {1 17 {249 27}} {puts "Result = [DbgNub_Do 1 {1 17 {265 9}} {Div3 10 0}]"}"
I can understand the debugger output from tclsh.exe but unable to interpret the debugger output from Komodo IDE.
especially, I am unable to understand DbgNub_Do 0 {1 17 {249 27}} {puts "Result = [DbgNub_Do 1 {1 17 {265 9}} {Div3 10 0}]"} what are the number shown in the lists (i.e {1 17 {249 27}}) and DbgNub_uplevelCmd 1 $cmd and DbgNub_Do
Thanks in advance
All the DbgNub_ commands mentioned in the stack trace are Komodo's extra debugging instrumentation (hiding them in the stack trace is theoretically possible, but messy and the instrumentation obviously doesn't actually do it). Generally, you probably ignore those commands as they're not going to be there when you deploy.
If we ignore the DbgNub_uplevelCmd bits, we get:
Info String for error
invoked from within
"Div $a $b"
invoked from within
"Div2 $a $b"
invoked from within
"Div3 10 0"
invoked from within
"DbgNub_Do 0 {1 17 {249 27}} {puts "Result = [DbgNub_Do 1 {1 17 {265 9}} {Div3 10 0}]"}"
That's recognisably similar to the standard trace except for the lack of line number information (and the DbgNub_Do, which is obviously instrumenting the code inside the catch directly).
Generally, Tcl's result status consists of:
A result code. This is TCL_OK (= 0) on success of a piece of code, TCL_ERROR (= 1) if an exception was thrown, and a few other values for other things (TCL_RETURN, TCL_BREAK and TCL_CONTINUE). Those names are defined at the C level.
A result value that is stored in the interpreter. In ancient versions of Tcl this was a simple string, but it's been a reference-counted typed value for over 20 years. (Tcl does have a type system. It's just very different from every other language's in that strings are the supertype of all other types.)
A result dictionary that contains additional information. Standard entries (it's possible to define your own too) in this are:
The error information, a.k.a stack trace that gets written to the errorInfo global to support old code. Only really meaningful for errors.
The error code, a machine readable exception descriptor that gets written to the errorCode global to support old code. Again, only really meaningful for errors.
The error line. The bit that's used to generate the number in the message (procedure "Div" line 1) and so on.
The level, which is used by return to do multi-level returns.
There's some other bits and pieces that only kick in in a few cases (such as when a try's finally clause throws an exception when the body of the exception threw, a gnarly case in lots of languages).
Related
Long strings which appear in tcl error messages are elided with ... after 150 characters:
proc helloWorld {a} {
throw
}
helloWorld "fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
Error:
invalid command name "throw"
while executing "throw "
(procedure "helloWorld" line 2)
invoked from within "helloWorld "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff..."
(file "mytcl" line 6)
Is there a way to change this so that more characters are shown?
In my large commercial tcl application, customers use file paths that are more than 150 characters long. We see 100 characters for a repository name, plus 100 characters for a location within the repository. Since only 150 characters appear, this means the "useful" part of the filename is not displayed in error messages. We have suggested to the customer that they could shorten the path using a symlink, but they do not accept this.
You can install your own background error handler.
You can log the error, display it how you want, etc.
interp bgerror {} ::bgerrhandler
# give err and res whatever names that make sense to you
proc ::bgerrhandler { err res } {
# do stuff
return
}
The problem that you've got is that the line-shortening is applied during the construction of the error trace message. It's already long gone by the time you get to see it. What you need instead is to install a custom background error handler using the new API that picks critical things out of the error stack. (The old API using a simple bgerror command is unsuitable for this task as it hides the result option dictionary for backward compatibility reasons.)
proc MyErrorHandler {msg opts} {
if {[dict get $opts -code] == 1} {
# Actually an error!
puts "ERROR: $msg"
foreach {op details} [dict get $opts -errorstack] {
puts stderr "$op :> $details"
}
return
}
# Transfer to standard implementation; tailcall recommended for this from 8.6 on
::tcl::Bgerror $msg $opts
}
interp bgerror {} MyErrorHandler
Here's an example of this working:
# Error generation code
proc foo {x} {throw BOOM $x}
proc bar {y} {foo $y$y$y$y}
proc grill {} {bar skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd}
# Now actually do things in the background
after idle grill; after 50 set z 1; vwait z
That will print this message:
ERROR: skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd
INNER :> returnImm skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd {-errorcode BOOM}
CALL :> foo skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsdskdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd
CALL :> bar skdfhglsdjklfgjhsdlfgjksdlkfhgklsdhfjklghskldfsldfkjghlsd
CALL :> grill
The INNER indicates what bytecode opcode actually threw, and then there's a sequence of CALLs to give the exact arguments lists that lead to that point.
Why do I get an error ?
#!/usr/bin/tclsh
proc add {a} {
uplevel 1 $a
puts $a
}
set n 0
add $n
I can't understand how uplevel works
uplevel executes some code in a different "stack frame" -- each time you invoke a procedure (and some other ways), Tcl adds an execution frame to the call stack.
Here's an example:
proc foo {} {
set fooVar 42
bar {expr {$fooVar + 21}}
}
proc bar {code} {
puts "in bar, code is: [list $code]"
puts "in bar, the fooVar variable [expr {[info exists fooVar] ? "does" : "does not"}] exist"
uplevel 1 $code
}
Running the foo procedure:
% foo
in bar, code is: {expr {$fooVar + 21}}
in bar, the fooVar variable does not exist
63
This is demonstrating that, because we're executing the code in one frame up in the call stack, the code fragment does have access to a local variable in that frame.
If we try to run bar with the same code block from the glocal scope, we'll
see an error:
% bar {expr {$fooVar + 21}}
in bar, code is: {expr {$fooVar + 21}}
in bar, the fooVar variable does not exist
can't read "fooVar": no such variable
But if we set the variable in the global scope, it works as expected:
% set fooVar -1
-1
% bar {expr {$fooVar + 21}}
in bar, code is: {expr {$fooVar + 21}}
in bar, the fooVar variable does not exist
20
Together with upvar, the uplevel command allows you to implement your own control structures -- Tcl is really an incredibly flexible language. An example:
proc foreachWithIndex {variableNames aList code} {
lassign $variableNames idxVar elemVar
upvar 1 $idxVar idx
upvar 1 $elemVar elem
set idx 0
foreach elem $aList {
uplevel 1 $code
incr idx
}
}
foreachWithIndex {i e} {a b c d} {puts "$i -> $e"}
0 -> a
1 -> b
2 -> c
3 -> d
If we run your code, we get the error message: invalid command name "0".
Let's investigate by printing the stack trace:
% puts $errorInfo
invalid command name "0"
while executing
"0"
("uplevel" body line 1)
invoked from within
"uplevel 1 $a"
(procedure "add" line 2)
invoked from within
"add $n"
That looks curious, but it is fine because the value of $a is 0 because the argument to add is the value of $n which is 0. The arguments to uplevel (after an optional first pseudo-numeric argument, which you are supplying; good!) are a script (with multiple arguments being concatenated). The string 0 can be a script, if you happen to have a command in scope whose name is the single digit 0, but that's pretty unusual.
Once it's got the script to run, uplevel 1 runs the script in the context of the caller of the current procedure. (Technically, it steps up 1 stack frame and runs it there. Tcl stack frames actually form a tree, though there's usually just one branch on the tree at a time; uplevel forms another branch.)
The two common forms of uplevel are uplevel 1 (for “run in the caller”) and uplevel #0 (for “run in the global scope”). There are others, but they're uncommon. Don't use uplevel 0; it's exactly like eval and confusion reigns if you get obscure like that.
It's good practice to usually keep your uplevel script forms to one of:
An argument passed in by the caller. (This is your script's case.)
A constant script.
A single command call generated by calling list. You can use {*}expansion when generating the list; this is a good way to handle a list of values or a command prefix.
All of these have been found to be generally non-confusing and fairly easy to use correctly (without weird hazards). Other options tend to be tricky.
I'm guessing your code should have actually been written like this:
proc add {varName} {
upvar 1 $varName v
set v [expr {$v + 1}]
# Or maybe: incr v
puts $v
}
set n 0
add n
The upvar command uses the same level resolution rules as uplevel, but instead aliases a named variable in that scope to the other local name in the current scope. A call to upvar #0 foo foo is functionally equivalent to the apparently simpler global foo.
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?
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).
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"
This is the code in TCL that is meant to produce factorial of a number given as parameter by the user.
if {$argc !=1}{
puts stderr "Error! ns called with wrong number of arguments! ($argc)"
exit 1
} else
set f [lindex $argv 0]
proc Factorial {x}{
for {set result 1} {$x>1}{set x [expr $x - 1]}{
set result [expr $result * $x]
}
return $result
}
set res [Factorial $f]
puts "Factorial of $f is $res"
There is a similar SO question, but it does not appear to directly address my problem. I have double-checked the code for syntax errors, but it does not compile successfully in Cygwin via tclsh producing the error:
$ tclsh ext1-1.tcl
extra characters after close-brace
while executing
"if {$argc !=1}{
puts stderr "Error! ns called with wrong number of arguments! ($argc)"
exit 1
} else
set f [lindex $argv 0]
proc Factorial {x}{..."
(file "ext1-1.tcl" line 3)
TCL Code from: NS Simulator for Beginners, Sophia-Antipolis, 2003-2004
Tcl is a little bit more sensitive about whitespace than most languages (though not as much as, say, Python). For instance, you can't add unescaped newlines except between commands as command separators. Another set of rules are that 1) every command must be written in the same manner as a proper list (where the elements are separated by whitespace) and 2) a command invocation must have exactly the number of arguments that the command definition has specified.
Since the invocation must look like a proper list, code like
... {$x>1}{incr x -1} ...
won't work: a list element that starts with an open brace must end with a matching close brace, and there can't be any text immediately following the close brace that matches the initial open brace. (This sounds more complicated than it is, really.)
The number-of-arguments requirement means that
for {set result 1} {$x>1}{incr x -1}{
set result [expr $result * $x]
}
won't work because the for command expects four arguments (start test next body) and it's only getting two, start and a mashup of the rest of other three (and actually not even that, since the mashup is illegal).
To make this work, the arguments need to be separated:
for {set result 1} {$x>1} {incr x -1} {
set result [expr {$result * $x}]
}
Putting in spaces (or tabs, if you want) makes the arguments legal and correct in number.
What is actually the difference between raising an exception in TCL via return -code error ...and error ...? When would one be used instead of the other?
The error command produces an error right at the current point; it's great for the cases where you're throwing a problem due to a procedure's internal state.
The return -code error command makes the procedure it is placed in produce an error (as if the procedure was error); it's great for the case where there's a problem with the arguments passed to the procedure (i.e., the caller did something wrong).
The difference really comes when you look at the stack trace.
Here's a (contrived!) example:
proc getNumberFromFile {filename} {
if {![file readable $filename]} {
return -code error "could not read $filename"
}
set f [open $filename]
set content [read $f]
close $f
if {![regexp -- {-?\d+} $content number]} {
error "no number present in $filename"
}
return $number
}
catch {getNumberFromFile no.such.file}
puts $::errorInfo
#could not read no.such.file
# while executing
#"getNumberFromFile no.such.file"
catch {getNumberFromFile /dev/null}
puts $::errorInfo
#no number present in /dev/null
# while executing
#"error "no number present in $filename""
# (procedure "getNumberFromFile" line 9)
# invoked from within
#"getNumberFromFile /dev/null"