Variable substitution in TCL within curly brace - tcl

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.

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.

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.

Use one list as multi variable, such as format argument

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?

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.

tcl error : extra characters after close-brace

having issues trying to debug this 'extra characters after close-brace' error. Error message points to my proc line ... I just can't see it for 2 days!
# {{{ MAIN PROGRAM
proc MAIN_PROGRAM { INPUT_GDS_OASIS_FILE L CELL_LIST_FILE } {
if { [file exists $CELL_LIST_FILE] == 0 } {
set celllist [$L cells]
} else {
set fp [open $CELL_LIST_FILE r]
set file_data [read $fp]
close $fp
set celllist [split $file_data "\n"]
set totalcells [expr [llength $celllist] - 1]
}
set counter 0
foreach cell $celllist {
set counter [expr {$counter + 1}]
set value [string length $cell]
set value3 [regexp {\$} $cell]
if { $value > 0 && $value2 == 0 && $value3 == 0 } {
# EXTRACT BOUNDRARY SIZE FIRST
puts "INFO -- READING Num : $counter/$totalcells -- $cell ..."
ONEIP_EXTRACT_BOUNDARY_SIZE $cell $L "IP_SIZE/$cell.txt"
exec gzip -f "IP_SIZE/$cell.txt"
}
}
# }}}
}
# }}}
This seems to be an unfortunate case of using braces in comments. The Tcl parser looks at braces before comments (http://tcl.tk/man/tcl8.5/TclCmd/Tcl.htm). It is a problem if putting braces in comments causes a mismatched number of open/close braces.
Try using a different commenting style, and remove the "{{{" and "}}}" from your comments.
I'm pretty sure that this is down to braces in comments within the proc body.
The wiki page here has a good explaination. In short a Tcl comment isn't like a comment most other languages and having unmatched braces in them leads to all
sorts of issues.
So the braces in the #}}} just before the end of the proc are probably the problem.
Tcl requires procedure bodies to be brace-balanced, even within comments.
OK, that's a total lie. Tcl really requires brace-quoted strings to be brace-balanced (Tcl's brace-quoted strings are just like single-quoted strings in bash, except they nest). The proc command just interprets its third argument as a script (used to define the procedure body) and it's very common to use brace-quoted strings for that sort of thing. This is a feature of Tcl's general syntax, and is why Tcl is very good indeed at handling things like DSLs.
You could instead do this:
proc brace-demo args "puts hi; # {{{"
brace-demo do it yeah
and that will work fine. Totally legal Tcl, and has a comment in a procedure body with unbalanced braces. It just happens that for virtually any real procedure, putting in all the required backslashes to stop interpretation of variable and command substitutions too soon is a total bear. Everyone uses braces for simplicity, and so has to balance them.
It's hardly ever a problem except occasionally for comments.