Use one list as multi variable, such as format argument - tcl

In Tcl
set f "%-3s %-3s %-3s"
set t {"aaa" "bbb" "ccc"}
puts [format $f $t]
I know it's incorrect, and it will return Error:
not enough argument
So how to correct it?

You can to use list expansion (as from Tcl 8.5):
set f "%-3s %-3s %-3s"
set t {"aaa" "bbb" "ccc"}
puts [format $f {*}$t]
In prior versions, you would have to use eval, which is not recommended
puts [eval format \$f $t]

As the number of specifiers and arguments to format is fixed, why not use lassign:
% lassign $t v1 v2 v3
% puts [format $f $v1 $v2 $v3]
aaa bbb ccc
In general, the expansion operator {*} is purposeful when the number of specifiers, and, therefore, arguments is somewhat dynamic. But in the static case, you end up with a more robust script when using lassign. Picture your list in t varying in size, while format expects exactly three value arguments?

Related

Variable substitution in TCL within curly brace

I'm having a problem where variables are in curly brace.I am trying to perform a variable substitution within curly braces which I understand is not feasible directly in TCL.But if there are other methods to resolve this? because I see the samiliar question in website that the answer is use list [] and others. But I want to countinue use curly brace, could someone can help me to resolve the question?
set top_design a
puts $top_design
puts {aaa %top_design}
output :
a
aaa %top_design
so how to display the subtitute of top_design in second puts.
Aside from the % typo, you're looking for the subst command:
set top_design a
puts {aaa $top_design} ;# => aaa $top_design
puts [subst {aaa $top_design}] ;# => aaa a
There are options to subst so that you have control over which things get substituted:
% puts [subst {aaa $top_design\n[clock seconds]}]
aaa a
1666273294
% puts [subst -nocommands -nobackslashes {aaa $top_design\n[clock seconds]}]
aaa a\n[clock seconds]
I think that you have messed up a little while forming the question.
In Tcl, { and } have special meaning, you can simpy say that this is an escape sequence or a list - depending on where it is used.
Let's see the following simple example:
set v1 aaa
set v2 bbb
set vres1 {$v1 $v2}
set vres2 "\{$v1 $v2\}"
set vres3 "\{\$v1 \$v2\}"
set vres4 {{$v1 $v2}}
This will result in the following output:
% set vres1 {$v1 $v2}
$v1 $v2
% set vres2 "\{$v1 $v2\}"
{aaa bbb}
% set vres3 "\{\$v1 \$v2\}"
{$v1 $v2}
% set vres4 {{$v1 $v2}}
{$v1 $v2}
For vres1, you have variables not evaluated as {} were used as an escape sequence.
For vres2, braces are escaped so are treated as a standard character, thus "" are needed to properly set all the characters as one argument for set command. Note that v1 and v2 are evaluated here as {} are not escape characters but characters that we are just saving in variable.
For vres3, we did manual escaping of braces and $ so that variables are not evaluated and braces are part of the string.
For vres4, first set of braces are escaping the second set and also $.
Now I guees, that you already have something like vres3 or vres4 and you want to get the values of the variables in braces.
When you have nested variables, you should use the eval command like below:
% eval set out1 \"$vres1\"
aaa bbb
% eval set out2 \"$vres2\"
{aaa bbb}
% eval set out3 \"$vres3\"
{aaa bbb}
% eval set out4 \"$vres4\"
{aaa bbb}
I hoped it helped in your issue!
[Ufff, my first answer!]
In Tcl 8.7, you'll be able to do this:
set top_design a
puts $top_design
set input {aaa %top_design}
puts [regsub -all -command {%(\w+)} $input {apply {{- varName} {
upvar 1 $varName var
# Ignore case where variable doesn't exist
return $var
}}}]
The key thing is that %top_design isn't very special at all to Tcl code; the % symbol is only meaningful in a few contexts (format, clock, expr, and Tk's bind). For it to have any other meaning, you have to apply that meaning yourself. That gets much easier with regsub -command (a new feature in 8.7) since that lets you use a command to generate the substitution within a regsub; that pairs well with apply though you could use a procedure instead. In earlier versions, such substitutions required non-trivial quoting and subst; I always found those things difficult to write correctly.

Tcl: Evaluate the variable value when passing argument to class

I have a piece of code like this
oo::class create class_test {
variable title_text
variable result
method title {t} {
set title_text $t
set result [format "%-6s %-6s" {*}$title_text]
}
method print {} {
return $result
}
}
set a "abcde"
set b "fghij"
class_test create foo
foo title {"$a" "$b"}
puts [foo print]
The real output is
$a $b
While the expected output is
abcde efghi
Could someone help to fix it?
Change
foo title {"$a" "$b"}
to
foo title [list $a $b]
so that the variables get substituted by their values.
You want to expand substitutions inside a {brace-quoted} string (logically) after the point that it is written in the script. This isn't usually recommended (not when you can construct arguments with list correctly), but you can do it.
method title {t} {
set title_text [lmap value $t {
uplevel 1 [list subst $value]
}]
set result [format "%-6s %-6s" {*}$title_text]
}
We do the transform on each word in the argument (lmap) and the transform is to apply subst to it, which must be done in the caller's context (uplevel 1). The use of list in there is so that we guarantee that we make a substitution-free script to run in the outer context, a very strongly recommended practice.
A feature of TclOO is that you don't need to take special precautions to use uplevel (or upvar) when using it, unlike some other older object systems for Tcl. That makes doing this sort of thing in a method no more tricky than doing it in a normal procedure. This is true even when inheritance is present.
Could someone help to fix it?
I fail to see why you, first, pack variable references into a single value and, then, uplevel-substitute them. In addition, the number of value arguments to format seem fixed. Why not just use two separate formal parameters to your title method and use them directly?
method title {v1 v2} {
set result [format "%-6s %-6s" $v1 $v2]
}
Then just call it like so:
foo title $a $b
Update
to generate the title in different length
then better use args like so?
method title {args} {
set result [format [join [lrepeat [llength $args] "%-6s"] " "] {*}$args]
}
args is the natural way of having a method (proc) with variable arguments in Tcl.

How to convert a tcl variable value to specific format

how can I convert
set var "USE_90a_Sc_ttv"
to
set out "9.0a ttv Sc"
using tcl code?
Regards,
Divesh
Use the split, lassign and regsub functions:
lassign [split $var _] prefix version type tag
regsub {(\d(\w)?)$} $version {.\1} nversion
set out "$nversion $tag $type"
If you are using an older version and don't have lassign available, you
can use lindex to retrieve specific items from the list returned by split.
set tlist [split $var _]
set version [lindex $tlist 1]
set type [lindex $tlist 2]
set tag [lindex $tlist 3]
regsub {(\d(\w)?)$} $version {.\1} nversion
set out "$nversion $tag $type"
I'd use scan to parse that, and list to assemble the results.
set var "USE_90a_Sc_ttv"
# Remember to check the result of [scan] for number of parsed fields
if {[scan $var {USE_%1d%2[^_]_%2[^_]_%3s%c} a b c d e] != 4} {
error "Unexpected input data! '$var'"
}
set out [list $a.$b $d $c]
Putting a %c at the end of the format lets me detect if there are any unexpected characters at the end. There shouldn't be; only 4 fields ought to be satisfied. This makes for a quick way to check that what I've got is what I expect. Also, %2[^_] is an unusual field specifier, but all it does is ask for 2 non-underscore characters.

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.

How to define a variable with argument expansion

The following command runs as expected:
lappend {*}{arr 1}
puts [lindex $arr 0]
Now I am trying to make a variable of "{*}{arr 1}" like this:
set X "{*}{arr 1}"
lappend $X
But this does not work, seems $X is taken as one whole value, argument expansion is not effective.
So is it a requirement that argument expansion can not be through variable?
The {*} is a syntactic feature of Tcl (from Tcl 8.5 onwards) just as […], "…" or $ is. You have to write it in the script in order for it to count as argument expansion; otherwise it's just a sequence of three characters.
If you want something like
set X "{*}{arr 1}"
lappend $X
to work, you need to pass it through eval:
set X "{*}{arr 1}"
eval lappend $X
Note that this then means that X actually contains a script fragment; this can have all sort of “interesting” consequences. Try this for size:
set X "{*}{arr 1};puts hiya"
eval lappend $X
Use of eval in modern Tcl is usually a sign that you're going about stuff the wrong way; the key use in old scripts was for doing things similar to that which we'd use {*} for now.
No, within double quotes, { and } actually lose their meaning, so will {*}. Notice that puts "{}" and puts {} are different.
The closest I can think of to do what you're trying to do would be to use something like this:
set X {arr 1}
lappend {*}$X
So if you now execute puts [lindex $arr 0], you get 1 as output.