tcl proc using upvar resulting in history(nextid) - tcl

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.

Related

info vars command is not working properly inside a proc

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.

How to get name of TCL test from the test itself

I was wondering how you would find the name of the test you're running in tcl from the test itself? I couldn't find this on google.
I'm calling another proc and passing the name of the test that is calling it, as an argument. So I would like to know which tcl command can do that for me.
This isn't an encouraged use caseā€¦ but you can use info frame 1 to get the information if you use it directly inside the test.
proc example {contextTest} {
puts "Called from $contextTest"
return ok
}
tcltest::test foo-1.1 {testing the foo} {
example [lindex [dict get [info frame 1] cmd] 1]
} ok
This assumes that you're using Tcl 8.5 or later, but Tcl 8.5 is the oldest currently-supported Tcl version so that's a reasonable restriction.
I read your comments ("source ... instade of my test name") as follows: You seem to source the Tcl script file containing the tests (and Donal's instrumented tcltest), rather than batch-running the script from the command line: tclsh /path/to/your/file.tcl In this setting, there will be an extra ("eval") stack frame which distorts introspection.
To turn Donal's instrumentation more robust, I suggest actually walking the Tcl stack and watching out for a valid tcltest frame. This could look as follows:
package req tcltest
proc example {} {
for {set i 1} {$i<=[info frame]} {incr i} {
set frameInfo [info frame $i]
set frameType [dict get $frameInfo type]
set cmd [dict get $frameInfo cmd]
if {$frameType eq "source" && [lindex $cmd 0] eq "tcltest::test"} {
puts "Called from [lindex $cmd 1]"
return ok
}
}
return notok
}
tcltest::test foo-1.1 {testing the foo} {
example
} ok
This will return "Called from foo-1.1" both, when called as:
$ tclsh test.tcl
Called from foo-1.1
and
$ tclsh
% source test.tcl
Called from foo-1.1
% exit
The Tcl version used (8.5, 8.6) is not relevant. However, your are advised to upgrade to 8.6, 8.5 has reached its end of life.

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.

What purpose does upvar serve?

In the TCL code that I currently work on, the arguments in each procedure is upvar'ed to a local variable so to speak and then used. Something like this:
proc configure_XXXX { params_name_abc params_name_xyz} {
upvar $params_name_abc abc
upvar $params_name_xyz xyz
}
From here on, abc and xyz will be used to do whatever. I read the upvar TCL wiki but could not understand the advantages. I mean why cant we just use the variables that have been received as the arguments in the procedure. Could anybody please elaborate?
I mean why cant we just use the variables that have been received as the arguments in the procedure.
You can. It just gets annoying.
Typically, when you pass the name of a variable to a command, it is so that command can modify that variable. The classic examples of this are the set and incr commands, both of which take the name of a variable as their first argument.
set thisVariable $thisValue
You can do this with procedures too, but then you need to access the variable from the context of the procedure when it is a variable that is defined in the context of the caller of the procedure, which might be a namespace or might be a different local variable frame. To do that, we usually use upvar, which makes an alias from a local variable to a variable in the other context.
For example, here's a reimplementation of incr:
proc myIncr {variable {increment 1}} {
upvar 1 $variable v
set v [expr {$v + $increment}]
}
Why does writing to the local variable v cause the variable in the caller's context to be updated? Because we've aliased it (internally, it set up via a pointer to the other variable's storage structure; it's very fast once the upvar has been done). The same underlying mechanism is used for global and variable; they're all boiled down to fast variable aliases.
You could do it without, provided you use uplevel instead, but that gets rather more annoying:
proc myIncr {variable {increment 1}} {
set v [uplevel 1 [list set $variable]]
set v [expr {$v + $increment}]
uplevel 1 [list set $variable $v]
}
That's pretty nasty!
Alternatively, supposing we didn't do this at all. Then we'd need to pass the variable in by its value and then assign the result afterwards:
proc myIncr {v {increment 1}} {
set v [expr {$v + $increment}]
return $v
}
# Called like this
set foo [myIncr $foo]
Sometimes the right thing, but a totally different way of working!
One of the core principles of Tcl is that pretty much anything you can do with a standard library command (such as if or puts or incr) could also be done with a command that you wrote yourself. There are no keywords. Naturally there might be some efficiency concerns and some of the commands might need to be done in another language such as C to work right, but the semantics don't make any command special. They all just plain commands.
The upvar command will allow you to modify a variable in a block and make this modification visible from parent block.
Try this:
# a function that will modify the variable passed
proc set_upvar { varname } {
upvar 1 $varname var
puts "var was $var\n"
set var 5
puts "var is now $var\n"
}
# a function that will use the variable but that will not change it
proc set_no_upvar { var } {
puts "var was $var\n"
set var 6
puts "var is now $var\n"
}
set foo 10
# note the lack of '$' here
set_upvar foo
puts "foo is $foo\n"
set_no_upvar $foo
puts "foo is $foo\n"
As it was mentioned in comment above, it is often used for passing function arguments by reference (call by reference). A picture costs a thousand words:
proc f1 {x} {
upvar $x value
set value 0
}
proc f2 {x} {
set x 0
}
set x 1
f1 x
puts $x
set x 1
f2 x
puts $x
will result in:
$ ./call-by-ref.tcl
0
1
With upvar we changed variable x outside of function (from 1 to 0), without upvar we didn't.

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