I am unable to print the desired 1 and 2 value of the variables in the foreach loop below. I am getting the name of the variable instead.
#A_voltages has 1
#B_voltages has 2
#port_1 has A
#port_2 has B
foreach v1 $port_1\_voltages {
foreach v2 $port_2\_voltages {
puts "-D3- v1=$v1= v2=$v2="
}
}
EXPECTED RESULT:
-D3- v1=1 v2=2
CURRENT RESULT:
-D3- v1=A_voltages v2=B_voltages
I'm not sure why you're trying to use nested loops, or loops at all if your variables have a single value each, but the trick is to build a variable name and pass it to set.
Example tclsh REPL session:
% set A_voltages 1
1
% set B_voltages 2
2
% set port_1 A
A
% set port_2 B
B
% puts "-D3 -v1=[set ${port_1}_voltages]= =[set ${port_2}_voltages]="
-D3 -v1=1= =2=
Note use of ${name} to set it apart from the rest of the string.
You may need to use the "array get", as in
#! /usr/bin/tclsh
set A_rray(0) "X"
set A_rray(1) "Y"
set A_rray(2) "Z"
set B_rray(0) "A"
set B_rray(1) "B"
set B_rray(2) "C"
set B_rray(3) "D"
foreach L { "A" "B" } {
puts $L
foreach {akey aval} [array get $L\_rray ] {
puts "$L $aval ($akey)"
}
}
which gives
A
A X (0)
A Y (1)
A Z (2)
B
B A (0)
B B (1)
B C (2)
B D (3)
It is often easiest to use upvar inside a procedure for this sort of thing. That lets you give variables easy-to-use names for the duration of the procedure. (Use #0 instead of 1 to get global names, or use namespace upvar :: instead of upvar.)
proc iterOver {port_1 port_2} {
upvar 1 ${port_1}_voltages p1v ${port_2}_voltages p2v
foreach v1 $p1v {
foreach v2 $p2v {
puts "-D3- v1=$v1= v2=$v2="
}
}
}
iterOver A B
For one-off code, a lambda can be used (with apply) as an alternative.
Related
I am trying to understand the difference between TCL arrays and TCL dictionary
There I get a statement :- "TCL array is a collection of variables while TCL dictionary is a collection of values"
Can somebody explain what it actually means. Programming example to understand the same will be great
Thanks
With an array, these are actually three separate variables.
set my_array(a) 1
set my_array(b) 2
set my_array(c) 3
Each variable exists separately:
info exists my_array(a) --> 1
info exists my_array(b) --> 2
info exists my_array(c) --> 3
puts "$my_array(a) $my_array(b) $my_array(c)" --> 1 2 3
The name my_array is also recognized to exist, but is actually a special name for the collection of the three individual variables. It's not really a regular variable that has its own value.
info exists my_array --> 1
puts $my_array --> can't read "my_array": variable is array
With a dictionary, there is one variable. The value of the variable represent the key/value pairs.
set my_dict "a 1 b 2 c 3"
info exists my_dict --> 1
info exists my_dict a --> wrong # args: should be "info exists varName"
puts $my_dict --> {a 1 b 2 c 3}
dict keys $my_dict --> {a b c}
dict get $my_dict a --> 1
dict values $my_dict --> {1 2 3}
In this example, the value of my_dict is {a 1 b 2 c 3}. You can pass $my_dict as an argument to a proc and the value is used by the proc. You can also return a dict from a proc. You cannot do that with an array in the same way.
One of the big differences is that you can trace add variable on an element of an array, whereas you can pass a dictionary into a procedure and return it from a procedure without needing to fiddle around with upvar or array get/array set.
proc didSetOnVarElement {name1 name2 op} {
puts "did $op on ${name1}($name2)"
}
trace add variable x(a) write didSetOnVarElement
set x(a) 1
set x(b) 2
incr x(a)
# Try comparing the output of that with what you get when you set a trace on the whole of x
Tracing array elements can be really useful, especially in Tk. So can passing things back and forth cheaply. You can't have them together though: values don't have identity in Tcl, whereas variables do (and it is exactly that identity — that name — that makes them modifiable and traceable entities while preventing them from being passed around trivially).
In addition to Chris's excellent answer:
A dictionary can be passed to a proc as a value
proc printItByValue {value} {
puts $value
}
set myDict [dict create a 1 b 2 c 3] ;# just another way to create a dict
printItByValue $myDict ;# => a 1 b 2 c 3
An array must be passed to a proc differently:
array set myArray {a 1 b 2 c 3} ;# just another way to populate an array
printItByValue $myArray ;# error as shown by Chris
printItByValue myArray ;# only prints the string "myArray"
We could extract the arrays contents and pass that to a proc as a list:
proc printArrayByValue {arrayContents} {
array set localArray $arrayContents
parray localArray
}
printArrayByValue [array get myArray]
localArray(a) = 1
localArray(b) = 2
localArray(c) = 3
parray is a handy utility proc to pretty-print an array.
Or, we can use the upvar command to link the passed variable name to the caller's stackframe.
proc printArrayByName {varName} {
upvar 1 $varName localArray
parray localArray
}
printArrayByName myArray ;# same output as above
Note that these procs print out the array using the local name. To get the array to print with the same name, we can do this tricky thing to link the local variable to the caller's variable with the same name:
proc printArrayByName {varName} {
upvar 1 $varName $varName
parray $varName
}
printArrayByName myArray ;# same output as above
myArray(a) = 1
myArray(b) = 2
myArray(c) = 3
Here is the code:
>cat /tmp/test_args.tcl
proc t1 {args} {
return $args
}
proc t2 {args} {
puts "t2:[llength $args]"
return
set len [llength $args]
if {$len == 1} {
proc_len_1 [lindex $args 0]
} elseif {$len == 2} {
proc_len_2 [lindex $args 0] [lindex $args 1]
} else {
proc_len_x $args
}
}
set tup3 [t1 1 2 3 4 5 6]
puts "before calling t2:[llength $tup3]"
t2 $tup3
t2 100
t2 100 200
Here is the output:
>tclsh /tmp/test_args.tcl
before calling t2:6
t2:1
t2:1
t2:2
I am using TCL 8.6.
You can see that before calling t2, $tup3 is a list, but proc t2 receives $tup3 as one single value, so instead of a list of values, proc t2 receives a list of list of values.
But the intention of proc t2, as see in the code after "return", is to deal with various number of arguments and based on the number of arguments it does different things. Now, calling t2 with a list variable and with a literal are treated same. This is the problem.
The only solution I can think of is, change
t2 $tup3
to
t2 {*}$tup3
But I have a restriction: $tup3 needs to stay same when it is passed to different proc. E.g. I can have such proc which also expects $tup3:
proc t3 {arg1} {
}
t3 $tup3
So ideally if somehow I can make it that "args" does not wrap values into a list, then my problem is solved. Well, I know this is how TCL works.
Maybe I already answered my own question, or I do not know what the I am looking for. If you see indeed there is a solution, please let me know.
Thanks.
If you want to pass a list around, simply accept it as an argument:
proc a { mylist } {
b $mylist
}
proc b { mylist } {
foreach {k} $mylist {
puts $k
}
}
set tup3 [t1 1 2 3 4 5 6]
a $tup3
Edit:
For a variable number of arguments, using command line processing is easiest.
proc a { args } {
array set arguments $args
if { [info exists arguments(-tup3)] } {
puts "tup3: $arguments(-tup3)"
}
if { [info exists arguments(-tup1)] } {
puts "tup1: $arguments(-tup1)"
}
parray arguments
}
set tup3 [list 1 2 3 4 5 6]
a -tup1 test1 -tup3 $tup3 -tup2 test2
Tcl, by design, makes it very difficult for a procedure (or C-defined command) to examine the syntax of how it was called. It's totally deliberate that it is that way, as it makes it massively easier to compose commands arbitrarily. Commands that need to care especially about the syntax of how they're called are recommended to perform an extra step to process their argument, with appropriate calls to do things in the environment of the caller (trivial in C, slightly trickier in Tcl procedures because of the extra stack frame).
proc example inputString {
# Parse the string and work out what we want to do
if {[regexp {^\$(\w+)$} $inputString -> varName]} {
upvar 1 $varName value
} else {
set value $inputString
}
# Do something with the result
puts "my input string was '$inputString'"
puts "my value is '$value'"
catch {
puts "its length is [llength $value]"
}
}
example {foo bar boo}
set x 123
example {$x}
This prints:
my input string was 'foo bar boo'
my value is 'foo bar boo'
its length is 3
my input string was '$x'
my value is '123'
its length is 1
You can get the calling syntax inside your procedure, but this is highly unrecommended except for debugging as it tends to produce information that is usually annoying to process. Here's how you get it:
proc example inputString {
puts "I was called as: [dict get [info frame -1] cmd]"
}
# To show why this can be awkward, be aware that you get to see *all* the details...
example {foo bar boo}
example "quick brown fox"
example [expr {1 + sqrt(rand())}]
set x 123
example $x
Which prints:
I was called as: example {foo bar boo}
I was called as: example "quick brown fox"
I was called as: example [expr {1 + sqrt(rand())}]
I was called as: example $x
The first approach above, passing in a literal that you parse yourself (with appropriate help from Tcl as required) is considered to be good Tcl style. Embedding a language inside Tcl (which can be Tcl itself, or some other language; people have shown this working with embedded C and Fortran, and there's no reason to expect any other language to be a big problem, though getting useful evaluation semantics can sometimes be… tricky) is absolutely fine.
I am new to Tcl so i am learning the basics. I wrote a function to calculate the sum of an array and print its elements. Here is the code
proc print_sum { tab } {
set s 0
foreach key [array names tab] {
puts "${key}=$tab($key)"
incr $s $tab($key)
}
puts "the sum = $s"
}
Here is how I called it:
print_sum tab
and I created the tab like this:
set tab("1") 41
set tab("m2") 5
set tab("3") 3
set tab("tp") 9
set tab("2") 7
set tab("100") 16
But the output is wrong! It outputs 0 instead of the actual sum and it does not output any element. But when I used the code directly without writing it in a function, it works.
The issue is that you're passing the string "tab" to the proc, and then you store that in the variable name "tab". This is just a plain variable, not an array, so when you do array names tab, you get an empty list back. The foreach loop loops zero times, and the sum is still zero.
You need to use the upvar command to link to the "tab" array in the caller's stack frame:
proc print_sum { arrayName } {
upvar 1 $arrayName a ;# "alias" the array in the caller's scope
set s 0
foreach key [array names a] {
puts "${key}=$a($key)"
incr s $a($key) ;# increment the *variable* not the *variablevalue*
}
puts "the sum = $s"
}
print_sum tab
outputs
"tp"=9
"100"=16
"1"=41
"2"=7
"3"=3
"m2"=5
the sum = 81
TCL Program Sample:
proc fun { x } {
puts "$$x = $x"
}
set a 10
fun $a
In this above program which prints the output as $10 = 10 But i would like to get a = 10 has the output. The variable which passes the values has to be read and the corresponding values as well. Is there a way to read the variable name.
proc fun name {
upvar 1 $name var
puts "$name = $var"
}
set a 10
fun a
The upvar command takes a name and creates an alias of a variable with that name.
Documentation:
proc,
puts,
set,
upvar
If you've got a currently-supported version of Tcl (8.5 or 8.6), you can use info frame -1 to find out some information about the caller. That has all sorts of information in it, but we can do a reasonable approximation like this:
proc fun { x } {
set call [dict get [info frame -1] cmd]
puts "[lindex $call 1] = $x"
}
set a 10
fun $a
# ==> $a = 10
fun $a$a
# ==> $a$a = 1010
Now, the use of lindex there is strictly wrong; it's Tcl code, not a list (and you'll see the difference if you use a complex command substitution). But if you're only ever using a fairly simple word, it works well enough.
% set x a
a
% set a 10
10
% eval puts $x=$$x
a=10
% puts "$x = [subst $$x]"
a = 10
% puts "$x = [set $x]"
a = 10
%
If you are passing the variable to a procedure, then you should rely on upvar.
What is the syntax for a proc in tcl which automatically takes arguments if user din't give any arg?
I used something like
proc a {{args 1}} {
puts $args
}
a
when I used this I dint get my args value as 1. It returns a blank string. Please help me with this.
It sounds like you're trying to do two different things together:
Accept a variable number of arguments to your proc
If no arguments are provided, use default value(s)
There isn't really a syntax for proc that handles this case specifically. However, it's fairly easy to accomplish. You can accept a variable number of arguments (args keyword), and then check to see if any were supplied (and use a default value if not).
proc myproc {args} {
if { [llength [info level 0]] < 2 } { #called with no args
set args {the default list of values}
}
# rest of the code goes here
}
The info level 0 command returns the actual command being run as it was called, with arguments. Hence, if the length of it's result is < 2, we know the current command was called with no arguments (since the first element in the list is the actual command name itself).
The word args is a reserved word that has a specific meaning in proc definitions. Use a different variable name:
proc a {{x 1}} {
puts $x
}
a
Additional answer
In a proc definition, default values are given by defining the argument as a list with two members: the first will be the name of the argument, the second is the default value.
The word args is a special argument that implements rest arguments (that is, it captures remaining arguments not specified in the argument list). This is how one can implement variadic functions in Tcl.
The args arguments and default values can be used together when defining procs. But args cannot have default values. Any argument listed before args can have default values. But arguments that have default values must be listed after arguments without default values. So, basically you can write a function like this:
proc a {x {y 1} args} {
puts "$x $y ($args)"
}
a 1 ;# prints 1 1 ()
a 1 2 ;# prints 1 2 ()
a 1 2 3 4 ;# prints 1 2 (3 4)
If your use-case meets this pattern then you can define arguments with default values before args like the example above. If not, then your only option is to process args yourself:
# Assume parameters are key-value pairs,
# if value not given then default to 1
proc b args {
foreach {x y} $args {
if {$y == ""} { set y 1 }
puts "$x -> $y"
}
}
b 1 2 ;# prints 1 -> 2
b 1 2 3 ;# prints 1 -> 2, 3 -> 1