Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Determining calling procedure

Status
Not open for further replies.

fhutt

Programmer
Jul 7, 2006
79
0
0
AU
Hello
I often write simple programs but end up with numerous procedures. When debugging my programs I often encounter the problem of a failing procedure using bad or wrong arguments. To track this down I would like to be able to determine the calling procedure.
Is there a mechanism in TCL/Tk to determine the calling procedure?
Thanks
 
I'm not entirely sure what you're asking. If you want to know the arguments of a procedure, the info args <procedure name> command will do it for you.

There are also many other options for the info command.

_________________
Bob Rashkin
 
I'll try to show you a simple example:

proc prog1 {} {
set arg1 25
set val1 [prog3 $arg1]
return $val1
}
proc prog2 {} {
set arg2 ab
set val2 [prog3 $arg2]
return $val2
}
proc prog3 {ghj} {
set val3 [expr $ghi + 3]
return $val3
}
set val4 [prog2]

Obviously in practice these procedures would be much more complicated. In running through this program, at the end, prog2 is called, then prog2 calls prog3 with the argument of 'ab' which will fail in proc3 as the expr tries to evaluate ab as an integer.
The program here being so simple, it's obvious that 'ab' is coming from proc2. But, in a complicated program is there a way to determine that the argument 'ab' came from proc2 or proc1 or even from the end?
Thanks
 
Maybe using of catch could help:
Code:
proc prog1 {} {
  set arg1 25
  set val1 [prog3 $arg1]
  return $val1
}

proc prog2 {} {
  set arg2 ab
  set val2 [prog3 $arg2]
  return -level 1 $val2
}

proc prog3 {ghj} {
  set val3 [expr $ghj + 3]
  return $val3
}

if {[catch {
  # catch the exception oy your code here
  set val4 [prog2]
  } errmsg]} {
  # if exception occured, parse trace info
  set trace_info $errorInfo
  # name of the procedure
  if {[regexp {\"(\S+)\"$} $trace_info match err_proc_name]} {
    #puts "match = $match"
    #puts "proc_name = $err_proc_name"
    puts "ERROR ocured in procedure '$err_proc_name'\n"
  }
  # error message  
  puts "ERROR Message is:\n$errmsg"
} else {
  # if everything ok then print result
  puts "OK with result $val4"
}
The result is
Code:
ERROR ocured in procedure 'prog2'

ERROR Message is:
invalid bareword "ab"
in expression "ab + 3";
should be "$ab" or "{ab}" or "ab(...)" or ...

When I change arg2 to valid value
Code:
proc prog2 {} {
  set arg2 123
  ...
}
then the result is
Code:
OK with result 126
 
Must admit your solution shows interesting ideas.
Unfortunately I don"t understand the coding in the regexp function. However, there are some problems with the result. The result shows that the problem is in proc2 with the expr function. But the expr function is in proc3, the called procedure.
Running this program, Tcl reports the error while running proc3 and reports that it is while invoking it from proc2, just what I am looking for. But, if the only way trap this error is with the catch function than it's too complicated because I can't include a catch function for each line of code. The bgerror doesn't catch it, I tried.
Relying on the Tcl error handler, unfortunately is problematic. I've seen a lot of error reports that don't fit on the page. This is where the problem is, it tries to show too much and I can't see the calling procedure reported.
I have a simple but awkward solution for this. Add an extra argument to each procedure. Let this argument be the name of the procedure itself. Then, for debugging, while the called procedure is running the calling procedure may be obtained just by looking at the argument;
proc prog1 {prog} {
set arg1 25
set val1 [prog3 $arg1 prog1]
return $val1
}

proc prog2 {prog} {
set arg2 ab
set val2 [prog3 $arg2 prog2]
return $val2
}

proc prog3 {ghj prog} {
set val3 [expr $ghj + 3]
return $val3
}
set val4 [prog2 inline]

Running this now, would issue an error in prog3 showing the expr to be at fault with the 'ab' element. Now where does this come from? placing a trap prior to expr function and display the variable prog. This would show prog2, the calling program, and where the problem lies:
proc prog3 {ghj prog} {
message_box $prog
set val3 [expr $ghj + 3]
return $val3
}
 
Btw, with the regular expression
Code:
\"(\S+)\"$
I parsed last nospace string which is enclosed into ".." from the global variable $errorInfo which contains for the case above this
Code:
invalid bareword "ab"
in expression "ab + 3";
should be "$ab" or "{ab}" or "ab(...)" or ...
    (parsing expression "ab + 3")
    invoked from within
"expr $ghj + 3"
    (procedure "prog3" line 2)
    invoked from within
"prog3 $arg2"
    (procedure "prog2" line 3)
    invoked from within
"prog2"
But I'm not sure what procedure name from $errorInfo you want...
you can get first, if you change the regular espression to
Code:
{procedure\s*\"(\S+)\"
i.e. you then get
Code:
ERROR ocured in procedure 'prog3'
or you can get last using the regular expression I used before
 
Thank you for the extra coding in the regexp. Unfortunately, like a lot of documentation, the manual page describes the 'regexp' for those who understand it. It has a couple of examples without explanation. For this reason I haven't used it in the past. My alternative has been the long handed approach as follows.
In the problem above I notice that the only way to trap the errorInfo is using the 'if {catch' statements. I tried to do this as follows:

proc err {} {
#Additional procedure
global errorInfo
set trace_info $errorInfo
set pos1 [string first {while} $trace_info]
set pos2 [string first {(procedure} $trace_info]
set err [string range [string range $trace_info 0 [expr $pos2 - 6]] 0 100]
set pos3 [string first {)} $trace_info $pos2]
set pro [string range $trace_info $pos2 $pos3]
set pos4 [string first {(procedure} $trace_info $pos3]
set pos5 [string first {)} $trace_info $pos4]
set invoke [string range $trace_info $pos4 $pos5]
bgerror "$err\n$pro\nInvoked from within\n$invoke"
}

if {[catch {
set val4 [prog2]
} errmsg]} {err
}
 
The procedure where error occured is the first entry of form
Code:
(procedure "proc_name" line n)
in the $errorInfo

I would try to do it with minimal intervention in the original code - something like this:
Code:
[COLOR=#804040][b]proc[/b][/color] prog1 {spam} {
  [COLOR=#804040][b]puts[/b][/color] [COLOR=#ff00ff]"* invoking prog01"[/color] 
  [COLOR=#804040][b]set[/b][/color] arg1 ab
  [COLOR=#804040][b]set[/b][/color] val1 [prog2 [COLOR=#008080]$arg1[/color]]
  [COLOR=#804040][b]return[/b][/color] [COLOR=#008080]$val1[/color]
}

[COLOR=#804040][b]proc[/b][/color] prog2 {foo} {
  [COLOR=#804040][b]puts[/b][/color] [COLOR=#ff00ff]"* invoking prog02"[/color]
[COLOR=#0000ff]  # here is the error  [/color]
  [COLOR=#804040][b]set[/b][/color] arg2 err
  [COLOR=#804040][b]set[/b][/color] val2 [prog3 [COLOR=#008080]$arg2[/color]]
  [COLOR=#804040][b]return[/b][/color] [COLOR=#008080]$val2[/color]
}

[COLOR=#804040][b]proc[/b][/color] prog3 {ghj} {
  [COLOR=#804040][b]puts[/b][/color] [COLOR=#ff00ff]"* invoking prog03"[/color] 
  [COLOR=#804040][b]set[/b][/color] arg3 [[COLOR=#804040][b]expr[/b][/color] [COLOR=#008080]$ghj[/color] + [COLOR=#ff00ff]3[/color]]
  [COLOR=#804040][b]set[/b][/color] val3 [prog4 [COLOR=#008080]$arg3[/color]]
  [COLOR=#804040][b]return[/b][/color] [COLOR=#008080]$val3[/color]
}

[COLOR=#804040][b]proc[/b][/color] prog4 {bar} {
  [COLOR=#804040][b]puts[/b][/color] [COLOR=#ff00ff]"* invoking prog04"[/color]   
  [COLOR=#804040][b]set[/b][/color] val4 [COLOR=#008080]$bar[/color]
  [COLOR=#804040][b]return[/b][/color] [COLOR=#008080]$val4[/color]
}

[COLOR=#0000ff]# main procedure[/color]
[COLOR=#804040][b]proc[/b][/color] main {} {
  [COLOR=#804040][b]set[/b][/color] val4 [prog1 [COLOR=#ff00ff]"eggs"[/color]]
  [COLOR=#804040][b]puts[/b][/color] [COLOR=#ff00ff]"OK with result [/color][COLOR=#008080]$val4[/color][COLOR=#ff00ff]"[/color]
}

[COLOR=#0000ff]# execution of main proc[/color]
[COLOR=#804040][b]if[/b][/color] {[[COLOR=#804040][b]catch[/b][/color] {main} result] } {
[COLOR=#0000ff]  # name of the procedure where error occured[/color]
  [COLOR=#804040][b]if[/b][/color] {[[COLOR=#804040][b]regexp[/b][/color] {\(procedure\s+\"(\S+)\"} [COLOR=#008080]$errorInfo[/color] match procname]} {    
[COLOR=#0000ff]    #puts "match = $match"[/color]
[COLOR=#0000ff]    #puts "proc_name = $procname"[/color]
    [COLOR=#804040][b]puts[/b][/color] [COLOR=#ff00ff]"ERROR ocured in procedure '[/color][COLOR=#008080]$procname[/color][COLOR=#ff00ff]'[/color][COLOR=#6a5acd]\n[/color][COLOR=#ff00ff]"[/color]
  }  
  [COLOR=#804040][b]puts[/b][/color] [COLOR=#ff00ff]"ERROR Description:[/color][COLOR=#6a5acd]\n[/color][COLOR=#ff00ff] [/color][COLOR=#008080]$result[/color][COLOR=#6a5acd]\n[/color][COLOR=#ff00ff]"[/color]
  [COLOR=#804040][b]puts[/b][/color] [COLOR=#ff00ff]"ERROR Trace Info :[/color][COLOR=#6a5acd]\n[/color][COLOR=#ff00ff] [/color][COLOR=#008080]$errorInfo[/color][COLOR=#6a5acd]\n[/color][COLOR=#ff00ff]"[/color]
}
The output is
Code:
C:\_mikrom\Work>tclsh85 fhutt.tcl
* invoking prog01
* invoking prog02
* invoking prog03
ERROR ocured in procedure [highlight]'prog3'[/highlight]

ERROR Description:
 invalid bareword "err"
in expression "err + 3";
should be "$err" or "{err}" or "err(...)" or ...

ERROR Trace Info :
 invalid bareword "err"
in expression "err + 3";
should be "$err" or "{err}" or "err(...)" or ...
    (parsing expression "err + 3")
    invoked from within
"expr $ghj + 3"
    (procedure "prog3" line 3)
    invoked from within
"prog3 $arg2"
    (procedure "prog2" line 6)
    invoked from within
"prog2 $arg1"
    (procedure "prog1" line 4)
    invoked from within
"prog1 "eggs""
    (procedure "main" line 2)
    invoked from within
"main"
It says that the error occured in prog3, however the real coding error is in prog2 in
Code:
  # here is the error  
  set arg2 err
But to find this error you have still to read the error info trace to see, that prog3 was invoked from prog2,...etc

When you correct the error - e.g.
Code:
  ## here is the error  
  #set arg2 err
  set arg2 123
then the code runs fine and produces the result
Code:
C:\_mikrom\Work>tclsh85 fhutt.tcl
* invoking prog01
* invoking prog02
* invoking prog03
* invoking prog04
OK with result 126
 
Yes, what you've done with this little program is great. It does show the procedure name where the problem is encountered and the procedure that causes the problem. It also shows the statement where the error occurs.
The only problem is, as I pointed out earlier, with errorInfo is that in a large program, the first section may be hundreds of characters long and goes off page (in some switch statements or others). Sometimes, the only way to continue is to shut down the program with Taskmanager because the ok button is off screen. This is the main reason for trying to extract the info needed and not display the errorInfo itself. In my program I extract only up to 100 characters from the first section.
 
Mikrom, just noticed a great idea. Instead of running the main program inline (level 0), run it in a procedure (main). By doing this, the catch can be placed inline, where main is called and thereby include all operations by the catch function. This is clever!
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top