So I have this piece of Tcl code that inherited. Essentially it does the following:
set LOG_ALL 0
set LOG_DEBUG 1
set LOG_INFO 2
set LOG_WARN 3
set LOG_ERROR 4
set LOG_FATAL 5
set LOG_SILENT 6
proc v2 {vimm {log LOG_DEBUG}} {
global LOG_DEBUG
if {$log == $LOG_DEBUG} {
puts "log"
} else {
puts "no log"
}
}
I suspect that the original idea of the designed was to use global variable for the default value of the log parameter. However, it isn't working as expected and I can't find how to write it correctly, assuming it even possible.
Which syntax will be correct?
Thank you for help.
Well, this would be correct:
proc v2 [list vimm [list log $LOG_DEBUG]] {
# ... body same as before
}
But that's just ugly. A neater way is:
proc v2 {vimm {log ""}} { # Any dummy value would do...
global LOG_DEBUG
if {[llength [info level 0]] < 3} {
set log $LOG_DEBUG
}
# ... as before
}
But the true Zen of Tcl is to not use numbers for this task at all, but rather names:
proc v2 {vimm {log "debug"}} {
if {$log eq "debug"} {
puts "log"
} else {
puts "no log"
}
}
Related
I'm trying to make a "safe" method of generating request ids for web sockets (just a desktop app not a real server) and want each socket to have its own id generator. All I'm doing is generating ids and recycling them after the request completes, such that the id doesn't grow unlimited throughout a user's session. I used an example concerning closures for a counter in JavaScript from David Flanagan's book and all seems to work well in Tcl but I'd greatly appreciate any advice on how to do this correctly and how I can test that these variables cannot be altered by the main program apart from calling one of the procedures within the namespaces. For example, is it possible to modify the gap list under the WEBS::$sock from the global namespace with [meant without] calling one of the procedures? Thank you.
Also, is there any difference between declaring namespace eval WEBS {} outside proc. ReqIdGenerator and using namespace eval WEBS::$sock inside the procedure? I can see that the results are the same for my little tests but wondered if there was any differences otherwise.
As an aside, in JS using the push and pop methods of arrays, it seems easier to recycle ids on a last-in-first-out basis; but using Tcl lists, it seems easier to use a first-in-first-out basis because using lassign with one variable assigns index 0 to the variable and returns the remaining elements as a new list. The equivalent of array.pop() seems to require more steps. Is that a correct observation? Thank you.
WARNING:
There is an error in this code in that the namespace references $sock and it works only because it is a global variable. If it were not global, the code would throw and error. The best I could find thus far is in this question.
proc ReqIdGenerator {sock} {
namespace eval WEBS {
namespace eval $sock {
variable max 0
variable gap {}
variable open {}
variable sock $sock
proc getId {} {
variable max
variable gap
variable open
if { [llength $gap] > 0 } {
set gap [lassign $gap id]
lappend open $id
return $id
} else {
lappend open [set id [incr max]]
return $id
}
chan puts stdout "Error in getId"
return -1
}
proc delId {id} {
variable max
variable gap
variable open
if { [set i [lsearch $open $id]] == -1 } {
return 1
} elseif { [llength $open] == 1 } {
reset
} else {
lappend gap [lindex $open $i]
set open [lreplace $open $i $i]
}
return 0
}
proc reset {} {
variable max 0
variable gap {}
variable open {}
}
proc getState {{prop "all"}} {
variable max
variable gap
variable open
variable sock
if { $prop eq "all" } {
return [list $max $gap $open]
} elseif { $prop eq "text" } {
return "State of socket $sock: max: $max; gap: $gap; open: $open"
} else {
return [set $prop]
}
}
}
}
}
set sock 123
ReqIdGenerator $sock
set sock 456
ReqIdGenerator $sock
# Add ids 1 through 10 to both sockets
for {set i 0} {$i<10} {incr i} {
WEBS::123::getId
WEBS::${sock}::getId
}
# Delete even ids from socket 456
for {set i 2 } {$i<11} {incr i 2} {
WEBS::${sock}::delId $i
}
# Delete odd ids from socket 123
for {set i 1 } {$i<10} {incr i 2} {
WEBS::123::delId $i
}
chan puts stdout [WEBS::123::getState text]
# => State of socket 123: max: 10; gap: 1 3 5 7 9; open: 2 4 6 8 10
chan puts stdout [WEBS::456::getState text]
# => State of socket 456: max: 10; gap: 2 4 6 8 10; open: 1 3 5 7 9
Lots of questions to unpack here.
how I can test that these variables cannot be altered by the main program apart from calling one of the procedures within the namespaces
You can't. There are no access controls within an interpreter. You can have multiple interpreters and there are strong access controls between them, but that's pretty heavyweight. However, it's conventional to not go rummaging around in a namespace that you don't own to peek at things you've not formally been told about on the grounds that they're liable to be changed at any moment without any sort of notification to you (usually not at runtime, but no guarantees!).
A phrase I've seen used in the community is "If you break it, you get to keep all the pieces".
For example, is it possible to modify the gap list under the WEBS::$sock from the global namespace with calling one of the procedures?
I'm sure it is. Finding it might be tricky, but once you have the name you can change it.
is there any difference between declaring namespace eval WEBS {} outside proc. ReqIdGenerator and using namespace eval WEBS::$sock inside the procedure?
There, assuming you handle the possible differences in name resolution scope of the name of the namespace itself. (That doesn't matter for fully qualified names — names beginning with :: — but relative names might resolve differently.)
The equivalent of array.pop() seems to require more steps. Is that a correct observation?
Yes. 8.7 adds lpop to address this weakness.
Your code appears to be reinventing objects. Use TclOO (or one of the other major object systems such as [incr Tcl] or XOTcl) for that; it's better at the job.
oo::class create ReqIdGenerator {
variable max gap open sock
constructor {sock} {
set max 0
set gap {}
set open {}
set [my varname sock] $sock; # messy because formal parameter
}
method getId {} {
if { [llength $gap] > 0 } {
set gap [lassign $gap id]
lappend open $id
return $id
} else {
lappend open [set id [incr max]]
return $id
}
chan puts stdout "Error in getId"
return -1
}
method delId {id} {
if { [set i [lsearch $open $id]] == -1 } {
return 1
} elseif { [llength $open] == 1 } {
my reset
} else {
lappend gap [lindex $open $i]
set open [lreplace $open $i $i]
}
return 0
}
method reset {} {
set max 0
set gap {}
set open {}
}
method getState {{prop "all"}} {
if { $prop eq "all" } {
return [list $max $gap $open]
} elseif { $prop eq "text" } {
return "State of socket $sock: max: $max; gap: $gap; open: $open"
} else {
return [set [my varname $prop]]
}
}
}
set sock 123
set s1 [ReqIdGenerator new $sock]
set sock 456
set s2 [ReqIdGenerator new $sock]
# Add ids 1 through 10 to both sockets
for {set i 0} {$i<10} {incr i} {
$s1 getId
$s2 getId
}
# Etc.
I am writing a proc to create a header in an output file.
Currently it needs to take an optional parameter, which is a possible comment for the header.
I have ended up coding this as a single optional parameter
proc dump_header { test description {comment = ""}}
but would like to know how I can achieve the same using args
proc dump_header { test description args }
It's quite easy to check for args being a single blank parameter ($args == ""), but doesn't cope well if passing multiple parameters - and I need the negative check anyway.
Your proc definition is incorrect (you'd get the error message too many fields in argument specifier "comment = """). Should be:
proc dump_header { test description {comment ""}} {
puts $comment
}
If you want to use args, you could examine the llength of it:
proc dump_header {test desc args} {
switch -exact [llength $args] {
0 {puts "no comment"}
1 {puts "the comment is: $args"}
default {
puts "the comment is: [lindex $args 0]"
puts "the other args are: [lrange $args 1 end]"
}
}
}
You might also want to pass name-value pairs in a list:
proc dump_header {test desc options} {
# following will error if $options is an odd-length list
array set opts $options
if {[info exists opts(comment)]} {
puts "the comment is: $opts(comment)"
}
puts "here are all the options given:"
parray opts
}
dump_header "test" "description" {comment "a comment" arg1 foo arg2 bar}
Some prefer a combination of args and name-value pairs (a la Tk)
proc dump_header {test desc args} {
# following will error if $args is an odd-length list
array set opts $args
if {[info exists opts(-comment)]} {
puts "the comment is: $opts(-comment)"
}
parray opts
}
dump_header "test" "description" -comment "a comment" -arg1 foo -arg2 bar
I use tcllib's cmdline library to do option parsing.
This is the example from cmdline documentation:
set options {
{a "set the atime only"}
{m "set the mtime only"}
{c "do not create non-existent files"}
{r.arg "" "use time from ref_file"}
{t.arg -1 "use specified time"}
}
set usage ": MyCommandName \[options] filename ...\noptions:"
array set params [::cmdline::getoptions argv $options $usage]
if { $params(a) } { set set_atime "true" }
set has_t [expr {$params(t) != -1}]
set has_r [expr {[string length $params(r)] > 0}]
if {$has_t && $has_r} {
return -code error "Cannot specify both -r and -t"
} elseif {$has_t} {
...
}
So, in your case, you'd just use args in place of argv in the above example.
It should be mentioned explicitly that args is a special word in Tcl that, when used at the end of the argument list, contains a list of all the remaining arguments. If no args are given, then no error is produced (unlike any other variable name, which would be considered a required argument).
I was looking for a way to have functionality similar to python's kwargs (optional key-value pair arguments), and something that works nicely is (similar to Glenn's last example):
proc my_proc {positional_required1 {positional_optional1 "a_string"} args} {
# Two optional arguments can be given: "opt1" and "opt2"
if {![string equal $args ""]} {
# If one or more args is given, parse them or assign defaults.
array set opts $args
if {[info exists opts(opt1)]} { set opt1 $opts(opt1) } else { set opt1 0 }
if {[info exists opts(op2)]} { set opt2 $opts(opt2) } else { set opt2 -1 }
} else {
# If no args are given, assign default values.
set op1 0
set op2 -1
}
# DO STUFF HERE
}
And can be called like:
my_proc "positional_required1_argument"
# OR
my_proc "positional_required1_argument" "a_string"
# OR
my_proc "positional_required1_argument" "a_string" opt1 7
# OR
my_proc "positional_required1_argument" "a_string" opt1 7 opt2 50
# etc.
A potential downside (as I've currently implemented it) is that if a user passes a non-approved key-value option, there is no error.
I have a Tcl utility that makes it easy to ensure a snippet of code run at the time control flow leaves the current scope (of the proc). It crashes in Tcl 8.6.6, so I'm wondering if there is a "better" way to implement the functionality in Tcl 8.6?
An example usage is:
proc test {file} {
set fh [open $file]
::Util::Defer [list close $fh]
# ... do a bunch of stuff
# and even if we hit an error
# [close $fh] will be evaluated as we return
# from the proc
}
It's worked great in Tcl 8.4, and I use it all over my code.
As I'm still coming up to speed on all the functionality available in Tcl 8.6, I'm asking how should the ::Util::Defer proc be written to best take advantage of Tcl 8.6?
Here is the 8.4 implementation:
namespace eval ::Util {}
proc ::Util::Defer_impl {cmd args} {
uplevel 1 $cmd
}
proc ::Util::Defer {cmd} {
set vname _u_defer_var
# look for a unique variable name
while {[uplevel 1 [list info vars $vname]] != ""} {
set vname ${vname}_
}
uplevel 1 [list set $vname $cmd]
# when the variable is unset, trigger a call to the command
uplevel 1 [list trace add variable $vname unset [list ::Util::Defer_impl $cmd]]
# return a chunk of code enabling the user to cancel this if desired
return [list variable $vname unset [list ::Util::Defer_impl $cmd]]
}
Edited to add:
I appreciate the answers. To be honest, I already have other syntactic sugar for a file handle, this:
proc test {file} {
set fh [::Util::LocalFileHandle $file]
# do stuff
}
I was just hoping more for a generic solution to the ::Util::Defer - because I occasionally have two or three uses (at different locations) in the same proc. Yes, I'm leaving out the error handling if the doesn't exist or isn't readable.
Note: I have reported the bug to ActiveState and filed a bug at core.tcl.tk.
Edited to add buggy code: This is the Tcl code that causes a crash for me, it is slightly pared down to the essence (as opposed to being the full-blown ::Util::Defer).
# ---------------begin script-------------------
package require Itcl
proc ::try_uplevel {} {
return [uplevel 1 [list ::info vars _u_defer_var]]
}
itcl::class ::test_class {
constructor {} {}
public proc test_via_proc {} {
::try_uplevel
}
}
::test_class::test_via_proc
# ---------------end script-------------------
The pattern you describe is a supported one; it shouldn't crash (and indeed I can't reproduce the crash with 8.6.3 or the tip of the 8.6 support branch). The only problem it has is that if you have an error during the close (or any other deferred script) it won't report it, as you can see from this snippet (% is prompt):
% apply {{} {
::Util::Defer [list error boo]
puts hi
}}
hi
%
This is part of why I went to quite a bit of effort to provide a try command in 8.6. With that, you can do this:
proc test {filename} {
set f [open $filename]
try {
# Do stuff with $f
} finally {
close $f
}
}
It also takes care of tricky things like stitching errors thrown inside the body and the finally clause together (the body exception info is in the -during option of the finally clause's error exception info) so that if both places error you can find out about both.
% catch {
try {
error a
} finally {
error b
}
} x y
1
% puts $x
b
% puts $y
-errorstack {INNER {returnImm b {}}} -errorcode NONE -errorinfo {b
while executing
"error b"} -errorline 5 -during {-code 1 -level 0 -errorstack {INNER {returnImm a {}}} -errorcode NONE -errorinfo {a
while executing
"error a"} -errorline 3} -code 1 -level 0
Personally, I'd be more inclined to write this:
proc withreadfile {varName filename body} {
upvar 1 $varName f
set f [open $filename]
try {
return [uplevel 1 $body]
} finally {
close $f
}
}
proc test {file} {
withreadfile fh $file {
# Do stuff with $fh
}
}
Your mileage may vary.
Untested code (this exact snippet, I've used this pattern many times):
proc test file {
try {
open $file
} on ok fh {
# do stuff with fh
# more stuff
} finally {
catch {close $fh}
}
}
should be about the same. Regardless of whether you handle errors with the try structure or not, (or whether you get errors or not) the code in the finally clause is run when it ends. If you want to be able to cancel the action, use a simple if inside the clause.
Edit
In case one wants to see any errors generated when the channel is closed, it's a bad idea to just wrap it in a catch, which is necessary if the file couldn't be opened and the channel-id variable wasn't created. Alternatives include:
Checking for existence: if {[info exists fh]} {close $fh}
Propagate the closing error: using the result and options variable name arguments to catch.
Over the weekend this heavyweight solution came to mind. It leverages the itcl::local functionality to achieve the same effect. It does depend on Itcl - but since the problem is an interaction with Itcl, that seems a reasonable solution, even though it is not purely Tcl.
itcl::class Defer_impl {
constructor {cmd} {} {
set _to_eval $cmd
}
destructor {
uplevel 1 $_to_eval
}
private variable _to_eval {}
}
proc ::Util::Defer {cmd} {
uplevel 1 [list itcl::local ::Defer_impl #auto $cmd]
}
I just started to learn Tcl. I wanted to write a simple procedure.
When the procedure starts, it opens a browse window to browse for files.
There you can select a file you want to open.
Then a pop-up windows comes up and asks if you want to selected another file.
Every file that you select has to go into an array.
I have to following code:
########## Defining the sub procedures ############
proc open_file {} {
set n 0
set title "Select a file"
set types {
{{GDS files} {.gds} }
{{All Files} * }
}
set filename [tk_getOpenFile -filetypes $types -title $title]
set opendFiles($n) $filename
set n [expr $n + 1]
set answer [tk_messageBox -message "Load another GDS file?" -type yesno -icon question]
if {$answer == yes } {
open_file
} else {
show_files ($opendFiles)
}
}
proc show_files {} {
foreach key [array names opendFiles] {
puts $opendFiles($key)
}
}
########## Main Program ###########
open_file
I having the following problems. Because I always recall the proc 'open_file' the variable $n keeps setting to 0. But I don't know how to recall the opening of the window without recalling the whole subroutine....
The second problem is sending the array to the next proc. When I send to the to the proc 'show_files', I always get the next error : can't read "opendFiles": variable is array.
I can't seem to find both answers..
You need global variables for that. This works for me:
########## Defining the sub procedures ############
set n 0
array set openedFiles {}
proc open_file {} {
set title "Select a file"
set types {
{{GDS files} {.gds} }
{{All Files} * }
}
set filename [tk_getOpenFile -filetypes $types -title $title]
set ::openedFiles($::n) $filename
incr ::n
set answer [tk_messageBox -message "Load another GDS file?" -type yesno -icon question]
if {$answer == yes } {
open_file
} else {
show_files
}
}
proc show_files {} {
foreach key [array names ::openedFiles] {
puts $::openedFiles($key)
}
}
########## Main Program ###########
open_file
Array Problem
In Tcl you can't send arrays to procs. You need to convert them to a list with array get send this list to the proc and than convert it back to an array again with array set.
Global variables are very useful at times, but I believe they are best avoided where possible. In this case I'd rather process the loop and the array in the main program rather than the proc.
Also, where you'd use an array in other programming languages, it's often better to use a list in Tcl, so something like:
proc open_file {} {
set title "Select a file"
set types {
{{GDS files} {.gds} }
{{All Files} * }
}
set filename [tk_getOpenFile -filetypes $types -title $title]
return $filename
}
proc show_files {files} {
foreach file $files {
puts $file
}
}
set openedFiles [list]
set answer yes
while {$answer == yes}
lappend openedFiles [open_file]
set answer [tk_messageBox -message "Load another GDS file?" -type yesno -icon question]
}
show_files $openedFiles
If you're into brevity, show_files could be written
proc show_files {files} {
puts [join $files \n]
}
and, now that it's so short, you could just put it in line, rather than have another proc.
Finally, have you considered what you want to do if the user presses cancel in tk_getOpenFile? In this case filename will be set to an empty (zero-length) string. You could either
ignore these; or
get rid of the tk_messageBox call and have the user press cancel when they have entered as many files as they want.
If you want to just ignore those times when the user pressed cancel, you could do
set filename [open_file]
if {[string length $filename] > 0} {
# The user entered a new filesname - add it to the list
lappend openedFiles $filesname
} else {
# The user pressed cancel - just ignore the filename
}
If you wanted to use cancel to break out of the loop, then the main program becomes something like:
set openedFiles [list]
set filename dummy
while {[string length $filename] > 0} {
set filename [open_file]
if {[string length $filename] > 0} {
lappend openedFiles $filename
}
}
show_files $openedFiles
in this case, you might want to put up a message box right at the start of the main program telling the user what's going on.
For the state of a variable to persist between calls to a procedure, you need to make that variable live outside the procedure. The easiest way is to use a global variable:
# Initialize it...
set n 0
proc open_file {} {
# Import it...
global n
...
# Use it...
set openedFiles($n) $filename
incr n
...
}
Arrays are not values, and as such can't be passed directly to another procedure. You can handle this by passing in the name and using upvar 1 to link a local alias to the variable in the calling stack frame:
proc show_files {varName} {
upvar 1 $varName ary
foreach key [array names ary] {
puts $ary($key)
}
}
Which is called using the name of the array, so no $:
show_files openedFiles
(You could also pass a serialization of the array in with array get openedFiles to serialize and array set ary $serialization to deserialize, but that carries some overhead.)
You probably ought to add that openedFiles variable to the global line, so that it is persistent across all invokations of open_file.
I am writing a proc to create a header in an output file.
Currently it needs to take an optional parameter, which is a possible comment for the header.
I have ended up coding this as a single optional parameter
proc dump_header { test description {comment = ""}}
but would like to know how I can achieve the same using args
proc dump_header { test description args }
It's quite easy to check for args being a single blank parameter ($args == ""), but doesn't cope well if passing multiple parameters - and I need the negative check anyway.
Your proc definition is incorrect (you'd get the error message too many fields in argument specifier "comment = """). Should be:
proc dump_header { test description {comment ""}} {
puts $comment
}
If you want to use args, you could examine the llength of it:
proc dump_header {test desc args} {
switch -exact [llength $args] {
0 {puts "no comment"}
1 {puts "the comment is: $args"}
default {
puts "the comment is: [lindex $args 0]"
puts "the other args are: [lrange $args 1 end]"
}
}
}
You might also want to pass name-value pairs in a list:
proc dump_header {test desc options} {
# following will error if $options is an odd-length list
array set opts $options
if {[info exists opts(comment)]} {
puts "the comment is: $opts(comment)"
}
puts "here are all the options given:"
parray opts
}
dump_header "test" "description" {comment "a comment" arg1 foo arg2 bar}
Some prefer a combination of args and name-value pairs (a la Tk)
proc dump_header {test desc args} {
# following will error if $args is an odd-length list
array set opts $args
if {[info exists opts(-comment)]} {
puts "the comment is: $opts(-comment)"
}
parray opts
}
dump_header "test" "description" -comment "a comment" -arg1 foo -arg2 bar
I use tcllib's cmdline library to do option parsing.
This is the example from cmdline documentation:
set options {
{a "set the atime only"}
{m "set the mtime only"}
{c "do not create non-existent files"}
{r.arg "" "use time from ref_file"}
{t.arg -1 "use specified time"}
}
set usage ": MyCommandName \[options] filename ...\noptions:"
array set params [::cmdline::getoptions argv $options $usage]
if { $params(a) } { set set_atime "true" }
set has_t [expr {$params(t) != -1}]
set has_r [expr {[string length $params(r)] > 0}]
if {$has_t && $has_r} {
return -code error "Cannot specify both -r and -t"
} elseif {$has_t} {
...
}
So, in your case, you'd just use args in place of argv in the above example.
It should be mentioned explicitly that args is a special word in Tcl that, when used at the end of the argument list, contains a list of all the remaining arguments. If no args are given, then no error is produced (unlike any other variable name, which would be considered a required argument).
I was looking for a way to have functionality similar to python's kwargs (optional key-value pair arguments), and something that works nicely is (similar to Glenn's last example):
proc my_proc {positional_required1 {positional_optional1 "a_string"} args} {
# Two optional arguments can be given: "opt1" and "opt2"
if {![string equal $args ""]} {
# If one or more args is given, parse them or assign defaults.
array set opts $args
if {[info exists opts(opt1)]} { set opt1 $opts(opt1) } else { set opt1 0 }
if {[info exists opts(op2)]} { set opt2 $opts(opt2) } else { set opt2 -1 }
} else {
# If no args are given, assign default values.
set op1 0
set op2 -1
}
# DO STUFF HERE
}
And can be called like:
my_proc "positional_required1_argument"
# OR
my_proc "positional_required1_argument" "a_string"
# OR
my_proc "positional_required1_argument" "a_string" opt1 7
# OR
my_proc "positional_required1_argument" "a_string" opt1 7 opt2 50
# etc.
A potential downside (as I've currently implemented it) is that if a user passes a non-approved key-value option, there is no error.