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 IamaSherpa on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Uplevel 1

Status
Not open for further replies.

marsd

IS-IT--Management
Apr 25, 2001
2,218
US
Hi,All.

Is there a way I can use an error checking function coded
problematically like this:

proc pooka {P pat {L1 ""} } {
set bdy [info body $P]
set i 0

foreach line [split $bdy "\n"] {
set i [incr i]
if {[regexp ".*(err..)" $line all m1 || [regexp ".*(err..).*" $line all m1]} {
if {[lsearch $L1 $m1] == 0} {
uplevel {puts [lindex $L1 [lsearch $L1 $m1]]}
}
} else {
puts "No poss match on line: $i."
}
}
return
}

Please forgive the cramped style here:
Where P is the calling proc's name, pat, is the pattern
(err*,in this case), and L1 would be [info locals err*].

The goal (which is impossible for me to figure out) is
how to use the locally derived variables from the error
matching procedure in the uplevel caller to get the exact error messages associated with the found errors.



An example:
Given the following:

proc bogus {} {
catch {puts "$foo"} err_1
catch {puts "$bar"} err_2

catch {puts "$foo$bar"} err_3
pooka bogus "err" [info locals err*]
return
}

I can get the lines and corroboration of the error names,
but not the exact error names to pass back to the caller
so I can access the error descriptions.
Is there an established way of doing this, or any way
at all to approximate this?

THANK YOU
MMD
 
I'm not sure this is something I can help with but I did notice that your 2nd condition: {[lsearch $L1 $m1] == 0} is only querying for your *err* to be element 0. Do you rather meand {[lsearch $L1 $m1] >= 0} ? Bob Rashkin
rrashkin@csc.com
 
Hm. I'm having difficulty figuring out exactly what you'd like your output to be. If you could give an example of what you were hoping to see when running your bogus procedure, I might be able to help out.

However, I do have a few other suggestions for your code...

The incr command actually moodifies the value of the variable provided as an argument. Therefore, the following was redundant:

Code:
set i [incr i]

You can get by with just:

Code:
incr i

Next, the regexp command will match the regular expression pattern you specify anywhere in the input string. So, instead of having the following in your if condition:

Code:
if {[regexp ".*(err..)" $line all m1] ||     [regexp ".*(err..).*" $line all m1]} {

You can simplify it down to:

Code:
if {[regexp "err.." $line m1]} {

Continuing through your code, I'm not quite sure what you're doing with:

Code:
if {[lsearch $L1 $m1] == 0} {

The lsearch command returns the index of the first element matching the pattern specified. So, this test will be true only if the err* variable in question is the first element of the list contained in L1. However, you said that L1 will contain information like the return value of info locals. The order of the variables returned by info locals is for all practical purposes random (it's walking a hash table of local variable names), so your test turns out to be whether a particular err* was listed first in a random list. I doubt that's what you really had in mind.

Finally, we've got your uplevel command:

Code:
uplevel {puts [lindex $L1 [lsearch $L1 $m1]]}

You {} quoted your argument to uplevel, so it doesn't get substituted by the interpreter when it parses the line. When the uplevel command then executes, it goes to the calling stack frame (the procedure that called your pooka procedure), and has the Tcl interpreter execute:

Code:
puts [lindex $L1 [lsearch $L1 $m1]]

The problem now is that the variables L1 and m1 don't exist in the scope of the calling procedure, they exist in the scope of the pooka procedure. So, you need to force evaluation of some of the code in the scope of the pooka procedure, and some of it in the scope of the calling procedure.

Without further analysis, I'll just present the code which properly evaluates what you're trying to execute in the code sample above. (However, as I indicated before, I don't think you're really getting the information you want from your lsearch command.)

Code:
uplevel [list puts [lindex $L1 [lsearch $L1 $m1]]]

Of course, in this case, all of the substitutions are performed in the scope of the pooka procedure, so there's no point in doing an uplevel on the puts. However, I suspect that this was only some test code, and that you had plans for obtaining further information that would eventually require the use of uplevel. - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Right.
Yes, and there is a missing brace on one of the regexps
too. Thanks for pointing that out!
I have this messy sprawling style of coding that is hard
pressed with forum margins. ;)
 
Thanks Avia.

Let me try to address the issues:

1)The regexp would need to derive the subpat m1, from
wherever it's location. Not the whole match, which is
garnered by [regexp "pat" $line all], but the exact
error string to pass back upscope to the caller in order
to access the exact error message produced when trying to
access the non-existent variable "$foo". (If possible)

2) The [info locals err*] will produce a list of
errors (see the bogus proc). The list is passed to the diagnostic "pooka" proc. The diagnostic first determines the line number of matching patterns and then searches the passed list for an exact match (typo: YES >= 0 NOT == 0) of the m1 string var for that line.
When it is determined thatthere is a list element matching (typo: lsearch -exact?) the problem occurs.

3) The problem is as you described it: The variables in
pooka do not exist in the caller, however the literal
value of $m1 is the list element from [info locals err*]
in the caller. So my problem was in how to replace the
variable with the literal string and pass it back to the caller to access the $err_1 error return in bogus {}.

On an ideal pass from a finished script the ouput would should look like->

pooka caught an error at:
line 4 : match = err_1
caller error = no such variable, etc..

Can you help with this?
Thanks to everyone.
 
Okay. I've got several points to respond to.

First, you were using an unnecessary level of subpatterns in your original regexp commands. You were looking for the pattern "err..", preceded by 0 or more of any characters, and followed by 0 or more of any characters. However, your procedure wasn't doing anything with the characters preceding or following the "err..." pattern. Therefore, they were extraneous to the search you really wanted to perform. To find the regular expression pattern "err.." anywhere in an input string, all you need is:

[tt]% regexp "err.." "Does err_1 appear in here?" m1
1
% set m1
err_1[/tt]

Secondly, given what you're attempting, we can actually simplify the way you're calling pooka with clever use of info level and upvar to examine our calling context. Here's a working example of what you were trying to do, but without needing to pass any arguments to pooka:

Code:
proc pooka {{pat err}} {
  # Just return if called from the global scope
  if {[info level] > 1} {
      
    # Get the name of the procedure that called us
    
    set procName [lindex [info level -1] 0]
    
    # Get the procedure's definition
    
    set procBody [info body $procName]
    
    # Get a list of the special error variable names
    # used in the calling procedure
    
    set vars [uplevel 1 [list info locals $pat*]]
    
    puts stderr "pooka caught an error in \"$procName\""
    
    set num 0
    foreach line [split $procBody \n] {
      
      # If the line contains one of our special
      # variable names, print a message on the
      # standard error channel

      if {[regexp "$pat.." $line var]
          && ([lsearch -exact $vars $var] >=0)} {
        
        puts stderr "line $num: match = $var"
        
        # Get the value of the variable in the
        # caller's scope
        
        set val [uplevel 1 [list set $var]]
        puts stderr "Caller error: $val"
      }
      incr num
    }
  }
}

An example of using this code:

[tt]% proc bogus {} {
catch {puts "$foo"} err_1
catch {puts "$bar"} err_2
catch {puts "$foo$bar"} err_3
pooka
return
}

% bogus
pooka caught an error in "bogus"
line 1: match = err_1
Caller error: can't read "foo": no such variable
line 2: match = err_2
Caller error: can't read "bar": no such variable
line 3: match = err_3
Caller error: can't read "foo": no such variable[/tt]

However, we've got a fundamental problem with your pooka. It still runs even if there were no errors. So even if there were no errors at all in your code, pooka would still mistakenly report non-existent errors.

Overall, it is going to take a lot of work -- and a fundamentally different approach -- for reporting the information you want. There has been some discussion of this on the comp.lang.tcl newsgroup (which you can read from Google Groups, My initial reaction is that what you're trying to accomplish could be better approached through a wrapper to the catch command. But as has been discussed in comp.lang.tcl, trying to report a line number from a procedure is a non-trivial task in Tcl.

I'll have to give this problem more thought... - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
I'm very impressed. Thank You!!
The regexp info comes as a complete shock. I have been misusing that for about a year :(
I am a hobbyist at programming, thats my only excuse.

About the diagnostic function:
Well, it seemed like a way to approach this problem...
Maybe a hybrid approach will reap a half-workable method.
I would still like to use the function args as they suit
my programming style better. The idea had occurred to me
of also being able to call the procedure from various stack depths as an additional arg. Your way definitely takes advantage of the tcl interpreter but I cannot follow it easily.
Thanks Again.
MMD

 
Avia,
The errors that are reported are seemingly caused by the catch functions init of the corresponding variable.
Even if the var string is empty it returns a null
character, or something of the sort.
Do you know where the C code is located in the tcl API where this is called from and where it would be in the source code?
Also:
As a temp measure a simple [string length $val] > 2
suffices to only keep "real" errors.
Thanks Again.
 
Well, bear in mind that there are two possible values assigned to the variable by a catch command:
[ul][li]In the case of an error, the error message is stored in the variable.[/li]
[li]In the case of successful execution, the return value of the last command executed is stored in the variable.[/li][/ul]
Thus, even your string length test will often produce "false positives." For example:

[tt]% catch {open hello.tcl} fid
0
% puts $fid
file1a4330[/tt]

The only reliable way to determine whether an error occurred or not is to test the return value of the catch statement itself. It returns 0 if no error occurred, and non-zero if an error occurred. (There are some other possibilities, but we don't need to worry about the too much in this case.)

That's why I suggested that the best way to tackle what you're attempting would be to create a wrapper for the catch command, and then use it something like:

[tt]proc bogus {} {
mycatch {puts "$foo"} err_1
mycatch {puts "$bar"} err_2
mycatch {puts "$foo$bar"} err_3
return
}[/tt]

To get some ideas for exploring your scheme, you might want to check out the implementations of a try...finally control structure as described on the Tcl'ers Wiki ( on the page You might also find it interesting to examine the implementation of the assert command in tcllib's control package. See for more information. - Ken Jones, President
Avia Training and Consulting
866-TCL-HELP (866-825-4357) US Toll free
415-643-8692 Voice
415-643-8697 Fax
 
Thank You Again avia, those are good sources.
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top