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?
--
+--------------------------------+---------------------------------------+
| Gerald W. Lester |
|"The man who fights for his ideals is the man who is alive." - Cervantes|
+------------------------------------------------------------------------+
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
> 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
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)
(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.
proc bb { } {
return [aa]
}
Stephan
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 |
|______________________________________________________________________|
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.
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.
>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
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.