Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

return in script executed by uplevel

75 views
Skip to first unread message

Arnt

unread,
Jul 18, 2007, 6:25:28 AM7/18/07
to

I'm trying to make a new looping construct for my script
(foreachlineinfile), which currently looks like this:

proc foreachlineinfile { inLineVarname inFilename inCmd } {
upvar $inLineVarname theLine

set theChan [open $inFilename "r"]
set theLines [read $theChan]

foreach theLine [split $theLines "\n"] {
uplevel 1 $inCmd
}

close $theChan

return $theResult
}

it works, except if inCmd contains a 'return' statement. If it
contains that, I would like to return from the function one level
above mine, that is, if I have:
proc a { } {
[..]
foreachlineinfile theLine "C:/text.txt" {
if { $theLine == "Stop" } {
return "Stop"
}
return "No Stop"
}

I'd like to see Stop as the return value of a. Currently, if my
testing does not deceive me, the loop ends when $theLine == "Stop",
but the return value is "No Stop". This does not seem to be the case
for the build-in looping and other constructs (like the 'if' in the
script above).
Is there an easy way to achieve this? (I can work around this by not
returning from that point certainly, but I'd like to learn what is
involved in making it work as I would expect.)

Or in a simplified script:

proc aa { } {
uplevel 1 {return a}
}
proc bb { } {
aa
return b
}

Calling bb returns b. what can I change to aa so that bb starts
returning a?

Arnt

unread,
Jul 18, 2007, 7:25:37 AM7/18/07
to
(Apologies if this reaches you twice, I don't see it in the group half
an hour after posting, so I'm trying agian.)

Gerald W. Lester

unread,
Jul 18, 2007, 9:31:17 AM7/18/07
to
I suggest taking a look at the ::fileutil::foreachLine procedure in the
fileutil package of TclLib to see how to correctly implement the desired
function.


--
+--------------------------------+---------------------------------------+
| Gerald W. Lester |
|"The man who fights for his ideals is the man who is alive." - Cervantes|
+------------------------------------------------------------------------+

Bryan Oakley

unread,
Jul 18, 2007, 10:00:47 AM7/18/07
to
Arnt wrote:
> I'm trying to make a new looping construct for my script
> (foreachlineinfile), which currently looks like this:
>...
> it works, except if inCmd contains a 'return' statement. If it
> contains that, I would like to return from the function one level
> above mine, ...

The only way for a proc to cause the caller to return is to do a "return
-code return".

So, the first step is to know that you must do "return -code return" if
the code you are given itself does a "return". How do you know that? How
can you detect if code passed in does a return?

The answer is to wrap the code in catch. While normally catch is used to
catch errors, it doesn't just return 0 or 1. As per the documentation,
it can return "1 (TCL_ERROR), 2 (TCL_RETURN), 3 (TCL_BREAK), and 4
(TCL_CONTINUE)" as well as zero (TCL_OK).

Thus, your code would look roughly like this:

proc foreachlineinfile { inLineVarname inFilename inCmd } {

...
set code [catch {
<run the passed-in code>
} result]
switch -exact -- $code {
2 {
# TCL_RETURN
return -code return $result
}
<handle other cases here>
}
}

You can see an example of this in working code here: http://wiki.tcl.tk/990

--
Bryan Oakley
http://www.tclscripting.com

Arnt

unread,
Jul 18, 2007, 10:58:25 AM7/18/07
to

"Bryan Oakley" <oak...@bardo.clearlight.com> wrote in message
news:j2pni.9402$eY....@newssvr13.news.prodigy.net...

> So, the first step is to know that you must do "return -code return" if
> the code you are given itself does a "return". How do you know that? How
> can you detect if code passed in does a return?
>
> The answer is to wrap the code in catch. While normally catch is used to
> catch errors, it doesn't just return 0 or 1. As per the documentation, it
> can return "1 (TCL_ERROR), 2 (TCL_RETURN), 3 (TCL_BREAK), and 4
> (TCL_CONTINUE)" as well as zero (TCL_OK).
>
> Thus, your code would look roughly like this:
>
> proc foreachlineinfile { inLineVarname inFilename inCmd } {
> ...
> set code [catch {
> <run the passed-in code>
> } result]
> switch -exact -- $code {
> 2 {
> # TCL_RETURN
> return -code return $result
> }
> <handle other cases here>
> }
> }

Thanks, that does looks a lot simpler than the discussion I've been trying
to read at http://wiki.tcl.tk/1507 . However, that discussion led me to TIP
90, which says that this should be made simpler in tcl8.5? So, perhaps I
should wait for 8.5, although your code seems so simple I might try it
anyway.

About TIP 90, do you know if it makes my code that doesn't work in 8.4 just
start to work in tcl 8.5, or will I still need to do something like the
example code you gave?

thanks!
Arnt


Bryan Oakley

unread,
Jul 18, 2007, 11:35:39 AM7/18/07
to

I think you will still need to do something like in the above example
for the case you specified (where the caller does a normal return from
the code passed in as a parameter)

Keith Nash

unread,
Jul 18, 2007, 10:17:38 PM7/18/07
to
> Or in a simplified script:
>
> proc aa { } {
> uplevel 1 {return a}
> }
> proc bb { } {
> aa
> return b
> }
>
> Calling bb returns b. what can I change to aa so that bb starts
> returning a?

(1) In 8.4 or 8.5 you can do:

proc aa { } {
uplevel 1 {return -code 2 a}
}

(2) In 8.5 only, you can do instead:

proc aa { } {
uplevel 1 {return -level 2 a}
}

(3) The man page for 'uplevel' does not define what 'return' should do - is
there an easy way to understand why Tcl behaves the way it does
('return'-ing from the uplevel code), and not the way that the original
poster expected? The man page for 'return' suggests that the latter
behaviour should be expected, i.e. return from the innermost procedure or
source script.


Keith.

skuh...@web.de

unread,
Jul 19, 2007, 6:23:18 AM7/19/07
to

Arnt wrote:
> Or in a simplified script:
>
> proc aa { } {
> uplevel 1 {return a}
> }
> proc bb { } {
> aa
> return b
> }
>
> Calling bb returns b. what can I change to aa so that bb starts
> returning a?

proc bb { } {
return [aa]
}

Stephan

Don Porter

unread,
Jul 19, 2007, 9:30:58 AM7/19/07
to
Keith Nash wrote:
>> Or in a simplified script:
>>
>> proc aa { } {
>> uplevel 1 {return a}
>> }
>> proc bb { } {
>> aa
>> return b
>> }
>>
>> Calling bb returns b. what can I change to aa so that bb starts
>> returning a?
>
> (1) In 8.4 or 8.5 you can do:
>
> proc aa { } {
> uplevel 1 {return -code 2 a}
> }

You could do that, but it's unnecessarily complicated. In this
context and in nearly all sensible contexts[*], it's completely
equivalent to:

proc aa { } {
return -code 2 a
}

That is, the [uplevel] is doing nothing useful at all.

[*] The arguably non-sensible exception is the case where the caller is
in some namespace where [return] refers to a command different from
[::return].

--
| Don Porter Mathematical and Computational Sciences Division |
| donald...@nist.gov Information Technology Laboratory |
| http://math.nist.gov/~DPorter/ NIST |
|______________________________________________________________________|

sleb...@gmail.com

unread,
Jul 19, 2007, 8:23:49 PM7/19/07
to
On Jul 18, 6:25 pm, Arnt <arnt.w...@gmail.com> wrote:
> I'm trying to make a new looping construct for my script
> (foreachlineinfile), which currently looks like this:
>
> <snip>

>
> I'd like to see Stop as the return value of a. Currently, if my
> testing does not deceive me, the loop ends when $theLine == "Stop",
> but the return value is "No Stop". This does not seem to be the case
> for the build-in looping and other constructs (like the 'if' in the
> script above).
> Is there an easy way to achieve this? (I can work around this by not
> returning from that point certainly, but I'd like to learn what is
> involved in making it work as I would expect.)
>

fileutil::foreachLine would do what you want. In any case, I also have
my own personal implementation of this function that I use (I wrote
it, like you, before I discovered fileutil). Here's my version which
handles return, catch and continue correctly:

proc eachline {var fname script} {
upvar 1 $var v
set f [open $fname r]
while {[gets $f v] >= 0} {
set code [catch {uplevel 1 $script} result]
switch -- $code {
0 {}
3 break
4 continue
default {
close $f
return -code $code $result
}
}
}
close $f
}

Notice that I use [gets] to read line by line so I only consume as
much memory as required to buffer a single line. Notice also that you
need the do nothing case when return code is 0 to allow the loop to
continue.

Here's a similar code I use for recursively scanning a directory:

proc dirscan {var path script} {
upvar 1 $var v
foreach v [glob -nocomplain -directory $path *] {
set code [catch {uplevel 1 $script} result]
switch -- $code {
0 {}
3 break
4 continue
default {
return -code $code $result
}
}
if {[file isdirectory $v]} {
dirscan $var $v $script
}
}
}

Notice the common error handling code.

Arnt

unread,
Jul 20, 2007, 6:10:34 AM7/20/07
to
On Jul 20, 2:23 am, "slebet...@yahoo.com" <slebet...@gmail.com> wrote:
> On Jul 18, 6:25 pm, Arnt <arnt.w...@gmail.com> wrote:
>
> > I'm trying to make a new looping construct for my script
> > (foreachlineinfile), which currently looks like this:
>
> fileutil::foreachLine would do what you want.

Actually, this is the reason I started making my own: I can't get
fileutil::foreachLine to do what I want. Is there any way to know if
there was a newline after the last line it passes you? In other words,
using fileutil::foreachLine I don't know how to differentiatie between
these 2 files:

file1 contents:
aaa<newline>
bbb<newline>

file2 contents:
aaa<newline>
bbb

(where newline obviously means there's a newline in the file right
there).

That is, if I do

set theFile [open "testout.txt" "w"]
fileutil::foreachLine a "testin.txt" {
puts $theFile $a
}

then testout.txt is the same (==file1) for each of the 2 files above.
I did not really see an easy way to find out that we are on the last
line and that there is no newline after that.

Helmut Giese

unread,
Jul 20, 2007, 6:53:20 AM7/20/07
to
On Fri, 20 Jul 2007 03:10:34 -0700, Arnt <arnt...@gmail.com> wrote:

>I did not really see an easy way to find out that we are on the last
>line and that there is no newline after that.

Well,
these are really very special requirements - after all, the actual
content of a file seems to be pretty much the same whether the last
line ends in a newline or not.
Under the circumstances I can think of I would consider it a good
thing that 'fileutil::foreachLine' behaves the same in both cases.
Maybe you can relax the requirements so that these 2 cases can be
handled like 1?

But if this distinction is so important how about some pre-processing?
a) If the files are not enormously large (nowadays everything under 1
MB normally is not 'enormously large') you could [read] the whole file
and find out about its last character.
a1) ... and if they are really big you can always [seek] to 'end-1'
and read 1 char.
You save this information somewhere and later act accordingly.
OR
b) After doing (a) you append a \n where it is missing - and then you
don't have 2 cases any more.
HTH
Helmut Giese

Gerald W. Lester

unread,
Jul 20, 2007, 8:43:10 AM7/20/07
to

Using gets you will never be able to tell the difference. You need to do
the following:

open the file for read
do a gets (ignore the results)
do an fconfigure -translation to see if it a CRLF or other
if CRLF, seek -2 character from the end, otherwise seek -1 char from end
do a read -nonewline
if the character read is equal to "\n" then the file ends in an EOF,
otherwise it does not.

Now if after that you want to process the entire file, seek to the begining
of the file.

0 new messages