info vars command is not working properly inside a proc - tcl

I am tring to do some variable auto-completion using TCL (this is intended for jimtcl)
I have tried the following sequence in both tclsh and jimsh:
% set VAR1 1
1
% set VAR2 2
2
% info vars
.... tcl_pkgPath VAR1 tcl_patchLevel VAR2 argc ...
% set pattern \$V*
$V*
% set vars_pattern [string range $pattern 1 end]
V*
% puts [lsort [info vars $vars_pattern]]
VAR1 VAR2
%
this is fine.
but once I get this into a proc
% proc autocomplete_helper pattern {
# check for variables auto-completion
puts "pattern '$pattern'"
if {[regexp {\$\S+$} $pattern match]} {
set vars_pattern [string range $match 1 end]
puts "pattern '$vars_pattern'"
return [lsort [info vars $vars_pattern]]
}
puts "other stuff to do"
}
% autocomplete_helper zerazer
pattern 'zerazer'
other stuff to do
% autocomplete_helper \$V*
pattern '$V*'
pattern 'V*
%
do you have any idea why this is not working ?

The info vars command is sensitive to what its current context (obviously; it returns the currently-visible variables) and moving things into a procedure changes that. The right fix for this is to use uplevel to run the command in a different context, either uplevel 1 to run in the caller's context or uplevel #0 to run in the global context (the one at the top of the stack).
In this case, we need to be a little careful because the pattern could have metacharacters in it (it'd be weird but legal) and uplevel is eval-like; the list command will ensure we've got a well-formed command. Putting this line into your procedure at the obvious point (everything else unchanged)
# The double quotes around #0 are to fool the highlighter used on Stack Overflow
return [lsort [uplevel "#0" [list info vars $vars_pattern]]]
With that, I can do this:
% autocomplete_helper {$e*}
pattern '$e*'
pattern 'e*'
env errorCode errorInfo
Which looks right to me.

This is a namespace problem.
A proc has its own namespace. When you're running info vars at the tclsh prompt, that's the global :: namespace.
The simplest thing to do in your proc would be to add :: to your argument to info vars
return [lsort [info vars ::$vars_pattern]]
The return values will include the :: namespace prefix, so remove that first if you need to.
Funny that you're seeing this problem with an auto-completion application. I've written a Tcl script to dump out all my procs, commands, namespaces, etc into json files that I read into Vim for a custom auto-completion plugin. I found the very same problems while writing that.

Related

pass square brace to uplevel TCL

I have a code in which I am passing a list to be evaluated, by TCl uplevel #0.
While it works well if I give it a code which uses curly braces in order to wrap the square braces, for example:
uplevel #0 { puts [ info vars CCK_* ] }
I cannot get to accept when I use a list, i.e.:
uplevel #0 [list puts "\[" info vars CCK_* "\]" ]
I get:
wrong # args: should be "puts ?-nonewline? ?channelId? string"
while executing
"puts {[} info var CCK_* \]"
("uplevel" body line 1)
invoked from within
"uplevel #0 [ list puts "\[" info var CCK_* "\]" ]"
I need the list command , because some of the rest of the code requires evaluation of variable names, that must happen before uplevel takes order ( i.e., input to uplevel). For example:
if { [ getpoint $elem ] == $pointy }
when [ getpoint $elem ] is to be evaluated in the uplevel, but pointy actually is defined and set in the calling proc , hence I cannot use curly braces for it, there will be evaluation before uplevel is called, and it would get just a number.Thanks,
You can only use the list command to build a single substitution-free command. It quotes everything for you specifically to do just that, and puts [info vars CCK_*] is a compound command. There are a few ways around this, but you should think carefully about what you're really doing:
I've quoted the #0 for reasons of highlighting only.
Only uplevel What Needs It
puts [uplevel "#0" [list info vars CCK_*]]
Or in this case:
puts [uplevel "#0" {info vars CCK_*}]
Wrap The Compound In eval
You can send arbitrary stuff like this, but I'm not sure why you'd do it:
uplevel "#0" [list eval { puts [ info vars CCK_* ] }]
Send A Lambda Term
uplevel "#0" [list apply {{} {
set vars [uplevel 1 {info vars CCK_*}]
puts $vars
}}]
It's not so useful here, but when you're wanting to send in arbitrary additional value from the current scope it becomes superb:
set value "this is a {complex string with \[some bits\] that might} make \$tcl choke"
uplevel "#0" [list apply {{value} {
set vars [uplevel 1 {info vars CCK_*}]
puts $value
puts $vars
puts $value
}} $value]
Assembling a script (or command sequence) to be submitted to uplevel etc. is not necessarily best achieved using list. This is the case for a script with nested evaluations, for instance.
Your question wording is not fully clear to me (so I might have interpreted it incorrectly), but you might want to consider using [subst] or [string map] for your purposes?
Watch:
set CCK_1 ""
proc foo {someVarName} {
uplevel "#0" [subst -nocommands {
if {"$someVarName" in [info vars CCK_*]} {
puts "Found $someVarName"
}
}]
}
foo CCK_1; # prints "Found CCK_1"
foo CCK_2
List are better suited for command sequences without excessive evaluation nesting; for complete scripts, better use script templates based on [subst] or [string map]. A word of caution: [subst] and [string map] don't protect the substitution values and position them in the script in their literal form.
Update
This is not to say that your original snippet could not be made to work:
set CCK_1 ""
# a) non-robust variant
proc bar {pattern} {
uplevel "#0" puts "\[info vars $pattern\]"
# equiv of
uplevel "#0" [concat puts "\[info vars $pattern\]"]
# versus
uplevel "#0" [list puts "\[info vars $pattern\]"]
}
bar CCK_*
set "CCK _1" ""
# b) robust variant
proc bar-robust {pattern} {
uplevel "#0" puts "\[[list info vars $pattern]\]"
# equiv of
uplevel "#0" [concat puts "\[[list info vars $pattern]\]"]
}
bar-robust "CCK _*"
uplevel assembles the script to be evaluated by [concat]ing its arguments. Like providing a single [concat]'ed the argument. You would not use list here to assemble the entire script, but rather to protect the script components under assembly (see bar-robust). Protection here means that complex values are maintained in their original meaning during script assembly (e.g., a match pattern incl. whitespace: CCK _*).
My recommendation would be to run the code in two steps. First, run the code in the square brackets, then use the result of that in the second call. Of course, since you're only doing a puts, there's no need to run that via uplevel:
set result [uplevel #0 [list info vars CCK_*]
puts $result
If using puts was for illustrative purposes, then I think the general idea of calling uplevel twice is still valid: run the code in square brackets as a distinct step, then combine it with your other code to get the final result.

How to pass arguments to tcl scripts when using tclsh [duplicate]

This is the code in TCL that is meant to produce factorial of a number given as parameter by the user.
if {$argc !=1}{
puts stderr "Error! ns called with wrong number of arguments! ($argc)"
exit 1
} else
set f [lindex $argv 0]
proc Factorial {x}{
for {set result 1} {$x>1}{set x [expr $x - 1]}{
set result [expr $result * $x]
}
return $result
}
set res [Factorial $f]
puts "Factorial of $f is $res"
There is a similar SO question, but it does not appear to directly address my problem. I have double-checked the code for syntax errors, but it does not compile successfully in Cygwin via tclsh producing the error:
$ tclsh ext1-1.tcl
extra characters after close-brace
while executing
"if {$argc !=1}{
puts stderr "Error! ns called with wrong number of arguments! ($argc)"
exit 1
} else
set f [lindex $argv 0]
proc Factorial {x}{..."
(file "ext1-1.tcl" line 3)
TCL Code from: NS Simulator for Beginners, Sophia-Antipolis, 2003-2004
Tcl is a little bit more sensitive about whitespace than most languages (though not as much as, say, Python). For instance, you can't add unescaped newlines except between commands as command separators. Another set of rules are that 1) every command must be written in the same manner as a proper list (where the elements are separated by whitespace) and 2) a command invocation must have exactly the number of arguments that the command definition has specified.
Since the invocation must look like a proper list, code like
... {$x>1}{incr x -1} ...
won't work: a list element that starts with an open brace must end with a matching close brace, and there can't be any text immediately following the close brace that matches the initial open brace. (This sounds more complicated than it is, really.)
The number-of-arguments requirement means that
for {set result 1} {$x>1}{incr x -1}{
set result [expr $result * $x]
}
won't work because the for command expects four arguments (start test next body) and it's only getting two, start and a mashup of the rest of other three (and actually not even that, since the mashup is illegal).
To make this work, the arguments need to be separated:
for {set result 1} {$x>1} {incr x -1} {
set result [expr {$result * $x}]
}
Putting in spaces (or tabs, if you want) makes the arguments legal and correct in number.

execute tcl commands line by line

I have a file like this:
set position {0.50 0.50}
set visibility false
set text {ID: {entity.id}\n Value: {entity.contour_val}}
And I want to do something similar to source, but I want to use a file handle only.
My current attempt looks like this:
proc readArray {fileHandle arrayName} {
upvar $arrayName arr
set cl 0
while {! [eof $fileHandle]} {
set cl [expr "$cl + 1"]
set line [gets $fileHandle]
if [$line eq {}] continue
puts $line
namespace eval ::__esg_priv "
uplevel 1 {*}$line
"
info vars ::__esg_priv::*
foreach varPath [info vars ::__esg_priv::*] {
set varName [string map { ::__esg_priv:: "" } $varPath]
puts "Setting arr($varName) -> [set $varPath]"
set arr($varName) [set $varPath]
}
namespace delete __esg_priv
}
puts "$cl number of lines read"
}
In place of uplevel I tried many combinations of eval and quoting.
My problem is, it either fails on the lines with lists or it does not actuall set the variables.
What is the right way to do it, if the executed commands are expected to be any valid code.
An extra question would be how to properly apply error checking, which I haven't tried yet.
After a call to
readArray [open "myFile.tcl" r] arr
I expect that
parray arr
issues something like:
arr(position) = 0.50 0.50
arr(text) = ID: {entity.id}\n Value: {entity.contour_val}
arr(visibility) = false
BTW: The last line contains internal {}, which are supposed to make it into the string variables. And there is no intent to make this a dict.
This code works, but there are still some problems with it:
proc readArray {fileHandle arrayName} {
upvar $arrayName arr
set cl 0
while {! [eof $fileHandle]} {
incr cl ;# !
set line [gets $fileHandle]
if {$line eq {}} continue ;# !
puts $line
namespace eval ::__esg_priv $line ;# !
foreach varPath [info vars ::__esg_priv::*] {
set varName [string map { ::__esg_priv:: "" } $varPath]
puts "Setting arr($varName) -> [set $varPath]"
set arr($varName) [set $varPath]
}
namespace delete __esg_priv
}
puts "$cl number of lines read"
}
I've taken out a couple of lines that didn't seem necessary, and changed some lines a bit.
You don't need set cl [expr "$cl + 1"]: incr cl will do.
if [$line eq {}] continue will fail because the [...] is a command substitution. if {$line eq {}} continue (braces instead of brackets) does what you intend.
Unless you are accessing variables in another scope, you won't need uplevel. namespace eval ::__esg_priv $line will evaluate one line in the designated namespace.
I didn't change the following, but maybe you should:
set varName [string map { ::__esg_priv:: "" } $varPath] works as intended, but set varName [namespace tail $varPath] is cleaner.
Be aware that if there exists a global variable with the same name as one of the variables in your file, no namespace variable will be created; the global variable will be updated instead.
If you intend to use the value in the text variable as a dictionary, you need to remove either the \n or the braces.
According to your question title, you want to evaluate the file line by line. If that requirement can be lifted, your code could be simplified by reading the whole script in one operation and then evaluating it with a single namespace eval.
ETA
This solution is a lot more robust in that it reads the script in a sandbox (always a good idea when writing code that will execute arbitrary external code) and redefines (within that sandbox) the set command to create members in your array instead of regular variables.
proc readArray {fileHandle arrayName} {
upvar 1 $arrayName arr
set int [interp create -safe]
$int alias set apply {{name value} {
uplevel 1 [list set arr($name) $value]
}}
$int eval [read $fileHandle]
interp delete $int
}
To make it even more safe against unexpected interaction with global variables etc, look at the interp package in the Tcllib. It lets you create an interpreter that is completely empty.
Documentation: apply, continue, eof, foreach, gets, if, incr, info, interp package, interp, list, namespace, proc, puts, set, string, uplevel, upvar, while

TCL/Expect - $argv VS $::argv VS {*}$argv

What is difference between following variables:
$argv
$::argv
{*}$argv
First two are possible to print via puts command and they returns following output:
param0 param1 {param 2} param3
param0 param1 {param 2} param3
The arguments that was passed to script were:
param0 param1 "param 2" param3
The last one end up with error:
wrong # args: should be "puts ?-nonewline? ?channelId? string"
while executing
"puts {*}$argv"
I've done some research in this area using following code:
if {[array exists $argv]} {
puts "\$argv IS ARRAY"
} else {
puts "\$argv IS NOT AN ARRAY"
}
if {[string is list $argv]} {
puts "\$argv IS LIST"
} else {
puts "\$argv IS NOT LIST"
}
if {[array exists $::argv]} {
puts "\$::argv IS ARRAY"
} else {
puts "\$::argv IS NOT AN ARRAY"
}
if {[string is list $::argv]} {
puts "\$::argv IS LIST"
} else {
puts "\$::argv IS NOT LIST"
}
if {[array exists {*}$argv]} {
puts "{*}\$::argv IS ARRAY"
} else {
puts "{*}\$::argv IS NOT AN ARRAY"
}
if {[string is list {*}$argv]} {
puts "{*}\$::argv IS LIST"
} else {
puts "{*}\$::argv IS NOT LIST"
}
The last two if-else statements which contain {*}$argv ends with following error:
wrong # args: should be "array exists arrayName"
while executing
"array exists {*}$argv"
invoked from within
"if {[array exists {*}$argv]} {
puts "{*}\$::argv IS ARRAY"
} else {
puts "{*}\$::argv IS NOT AN ARRAY"
}"
Commenting out those two statements shows that $argv and $::argv are lists:
argv IS NOT AN ARRAY
$argv IS NOT AN ARRAY
argv IS LIST
$argv IS LIST
Both those lists can be traversed as standard list e.g.:
foreach item $argv {
puts $item
}
or
foreach item $::argv {
puts $item
}
Attempt to traverse {*}$argv the same way leads to following error again:
wrong # args: should be "foreach varList list ?varList list ...? command"
while executing
"foreach item {*}$argv {
puts $item
}"
I am using TCL version 8.5
What is difference between following variables:
$argv
$::argv
{*}$argv
There are two types of difference here.
Unqualified and Qualified Variables
In Tcl, unqualified and qualified variables can be a bit different, but it depends on the context (in a pretty simple way though). Firstly, a qualified variable name is one that contains at least one :: within it. If the variable name (the thing after the $ — in Tcl, $ just means “read this variable now and use its contents here”) starts with ::, it is an absolute variable name, otherwise a qualified variable name is a relative variable name and is resolved with respect to the current namespace (which you can find out with namespace current if you're uncertain). Absolute variable names always refer to the same thing, in all contexts. Thus, ::argv is an absolute variable name, and indeed it refers to a variable called argv in the top-level, global namespace. That happens to be a variable that tclsh and wish write their arguments into.
But if there is no ::, it is an unqualified variable name. If you are not in a procedure (or procedure-like thing, which includes a lambda term such as you'd use with apply or the methods defined by various OO systems) then the variable is (mostly) treated as if it was a relative variable name and resolved with respect to the current namespace. namespace eval and namespace code are two of the things that can change the current namespace (the others are more obscure). All this is provided you use variable to declare all your namespace variables. Otherwise, you can hit some weird problems with variable resolution which are really nasty. So do use variable. Really.
If you are in a procedure(-like entity) though, that unqualified name refers to a local variable, whose life is coupled to that of the stack frame pushed on the stack when the procedure is entered. That can be linked to variables in other scopes (including the global namespace) through various commands: global, upvar, variable, and namespace upvar. However, the actual resolution of the variable is to something local.
Finally, there might also be a custom variable resolver in place. Since you're using Tcl 8.5, the place where you're most likely to see this in use is if you're using Incr Tcl, an object system for Tcl. Custom variable resolvers can do some complex stuff. (If you were using Tcl 8.6, the most likely place to see a custom variable resolver at work is in TclOO. The variable resolver there is very conservative and cautious, but allows local variables to be bound to object variables without having to explicitly declare this in each method).
Normal and Expanding Substitution
The difference between $argv and {*}$argv is totally different.
$argv is a normal substitution. It says “read this variable here and use the contents of it instead”. It can be used in the middle of a word, so $argv$argv$argv is a thing, consisting of the concatenation of the contents of the argv variable three times.
{*}, when placed at the start of a word (it's not special elsewhere), marks that word for expansion. When a word is expanded, it's parsed as a Tcl list after all other normal substitutions have been done, and the words of that list are used as words in the resulting command being built up. {*}$argv is a degenerate case where the remainder of the word is just the a read from a variable; the words that are used in the command are the elements of the list in the argv variable. Since that's normally a list, this is all hunky-dory.
Here's an example:
set abc {a b c}
set dabcf [list d $abc f]
puts $dabcf; # ===> “d {a b c} f”
set eabcg [list e {*}$abc g]
puts $eabcg; # ===> “e a b c g”
See the difference? One produces three elements in the list, the other produces five. It makes even more sense with something somewhat longer:
set options {
-foreground blue
-background yellow
-text "This is eye-watering stuff!"
}
button .b1 {*}$options -command {puts "Ouch 1"}
button .b2 {*}$options -command {puts "Ouch 2"}
button .b3 {*}$options -command {puts "Ouch 3"}
pack .b1 .b2 .b3
With expansion, that all Just Works™. Without, you'd have to do something horrific with eval:
eval [list button .b1] [lrange $options 0 end] [list -command {puts "Ouch 1"}]
# etc.
This was difficult to get right, and tedious, so it caused lots of people (including Tcl and Tk maintainers!) many problems because they tended to take shortcuts and get it wrong. It was to address this that expansion syntax was created in Tcl 8.5 to make this all less error prone. (The prototypical example in plain Tcl tends to involve things with exec, and meant that quite a few people actually had security holes because of this.)
As a bonus, using {*} is much faster than using eval since expansion can guarantee that it is never doing complicated reparsing of things. In Tcl, faster virtually always correlates with safer.
Note that this is independent of whether the variable is qualified. Yes, that means that you can also have {*}$::argv if you want.
You confuse the effects of substitution with the effects of argument expansion.
Please study the Dodekalogue http://wiki.tcl.tk/10259.
You mix the Rule #5: Argument Expansion (the {*} thing) with Variable Substitution (Rule #8).
The three forms you listed above are equivalent to the following:
$argv -> [set argv]
Get the value of a simple variable in the currently active scope.
$::argv -> [namespace eval :: { set argv }] -> [set ::argv]
Get the value of the variable in the namespace :: (the global namespace)
{*}$argv -> [eval [set argv]]
Expand the variables content to multiple arguments.

tcl proc using upvar resulting in history(nextid)

I'm getting this weird issue.
i'm using tcl 8.3
after i define this proc in a tcl shell
% proc incr { varName {amount 1}} {
puts $varName
upvar #0 $varName var
puts $varName
if {[info exists var]} {
set var [expr $var + $amount]
} else {
set var $amount
}
return $var
}
i keep getting
%
history(nextid)
history(nextid)
history(oldest)
history(oldest)
%
Everytime i hit newline "Enter" after that
any one has any idea why this is happening?
Because the history managment is written in Tcl itself, and that uses incr.
Your incr is almost equal to Tcl 8.3's incr with some differences:
The name of the variable is always printed
If the variable does not exist, it will be created.
So if you remove the first difference (the puts) everything will work as expected, just that some library commands may call your incr instead the standard incr.
The second difference is now in the core, IIRC starting with Tcl 8.5 it is not nessencary that a variable already exists pior to calling incr.
In short: What you did is fine. But don't expect to be the only one who calls an standard command.