How to define a variable with argument expansion - tcl

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.

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.

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.

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 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.

Tcl: write lambda and map using tcl 8.6

How do I write and apply a simple lambda function using the tcl 8.6 features "apply" and "lmap"?
map (lambda x -> x*x) [list 1 2 3]
how can I write the above in Tcl 8.6? The man pages are not that self explanatory for me.
Perhaps also a more advance version, but I guess I can figure that out myself:
lambda y -> map (lambda x -> x*x) y
Basically I would like to improve this version:
proc \x {f val} {
set res [apply $f $val]
set res
}
set res [\x {x {expr $x*$x}} 5]
puts "res: $res"
So that I can just write:
set res [\x {expr $x*$x} 5]
puts "res: $res"
Here's what lambda looks like:
proc lambda {arguments expression} {
list ::apply [list $arguments [list expr $expression]]
}
Then we do this, noting that {*} is required because the inner lambda term can't be a command directly without causing other trouble that we didn't want to have in 8.5 (or 8.6):
set res [lmap x [list 1 2 3] {
{*}[lambda x {$x * $x}] $x
}]
The 8.6 lmap is syntactically like foreach, so an extra layer of application is needed. It is, however, more easily understood by average Tcl programmers for being like that.
Note that the lambdas are fully first-class values that can be passed around however you want (put in variables, returned, stored in a list, whatever):
set square [lambda x {$x * $x}]
puts "the square of 42 is [{*}$square 42]"
(You could use λ for a command name if you wanted, but I find it awkward to type on this keyboard. I don't recommend using \x though; Tcl uses backslashes for various escaping duties.)