How to escape special characters while using lsearch? - tcl

How to escape special characters(e.g "[]") while using search?
Consider the following scenario:
>> set L { a b c [] }
>> a b c []
>> lsearch $L b
>> 1
>> lsearch $L "[]"
>> -1
I'm looking to get 3 when I run lsearch $L "[]"

When looking for fixed strings rather than patterns, it is easiest to use the -exact option to lsearch. You also need to make sure Tcl doesn't do substitution on the search string, for example by enclosing it inside curly braces. Otherwise you'll tell Tcl to look for an empty string (the result of executing an empty command string):
lsearch -exact $L {[]}

Related

Is there a simple way to parse a line of Tcl into its command and its arguments (not just splitting by whitespace)

Suppose I have a string which is also a Tcl command.
set line {lsort -unique [list a b c a]}
How can I convert this string into a list equivalent to this?
{
{lsort}
{-unique}
{[list a b c a]}
}
Because of whitespace inside the square brackets, I can't just use lindex.
For example:
> lindex $line 2
--> [list
The reason I'm asking is because I have a large Tcl script that I want to parse and re-write. I would like certain lines in the re-written script to have swapped argument order or some numerical arguments scaled by a factor.
I know I could parse the string character by character, keeping track of {}, [], and " characters, but this feels like re-inventing something that might already exist. I've been looking at the info and interp commands but couldn't find anything there.
I used info complete successfully in this proc.
proc command_to_list {command} {
# split by whitespace
set words [regexp -all -inline {\S+} $command]
set spaces [regexp -all -inline {\s+} $command]
set output_list [list]
set buffer ""
foreach word $words space $spaces {
append buffer $word
if {[info complete $buffer]} {
lappend output_list $buffer
set buffer ""
} else {
append buffer $space
}
}
return $output_list
}
This proc will group whitespace separated 'words' until they have no unmatched curlies, double quotes, or square brackets. Whitespace is preserved inside of matching pairs of curlies, double quotes or square brackets.
> set command {foreach {k v} [list k1 v1 k2 v2] {puts "$k $v"}}
> foreach word [command_to_list $command] {puts $word}
foreach
{k v}
[list k1 v1 k2 v2]
{puts "$k $v"}

Using tcl split to solve `list element in braces followed by "]" instead of space` causes `unmatched open brace in list`

(This is from a nagelfar plugin -- it's a tcl analyzer written in tcl, which is why $x contains tcl code.)
In tcl shell:
% set x {proc {::$p} args {[subst { foo }]} }
proc {::$p} args {[subst { foo }]}
%
% lindex $x 3 0
list element in braces followed by "]" instead of space
According to http://forum.egghelp.org/viewtopic.php?t=2603 the solution is to use split, however:
% lindex [split $x] 3 0
unmatched open brace in list
What's the correct way to use lindex on a variable whose content is like the above $x?
The original string $x can be treated like a simple list...
% set x {proc {::$p} args {[subst { foo }]} }
% foreach item $x { puts $item}
proc
::$p
args
[subst { foo }]
...but not as a list of lists. [subst { foo }] is not formatted properly to be a list.
% lindex $x 3 0
Error: list element in braces followed by "]" instead of space
Use error_info for more info. (CMD-013)
A string can be treated like a list in Tcl if it can be separated by white space and optionally includes grouping with quotes or braces. The last white space separated part of the string [subst { foo }] is }]. This prevents first { character from forming a match for grouping.
For this specific string, you could first get the 4th item from $x and the use split to get the 1st item.
% lindex [split [lindex $x 3]] 0
[subst
In a general case, you could insert a space after every occurence of }.
% set y [regsub -all "\}" $x "\} "]
proc {::$p} args {[subst { foo } ]}
% lindex $y 3 0
[subst

Working with lists that have words ending in semi-colons?

Is there a way to add strings (words) that end with a semi-colon to a list without it being a single-item list itself?
I'm working with strings and breaking them into words; and the punctuation often is attached as a suffix. I need to have the words both ways, that is with and without the punctuation.
Is it okay to simply let it a be a list within a list, and just reference all of the words as if they were single-item lists? It appears to work. Or is there a better method altogether?
Thank you.
set list { a; b c d }
chan puts stdout $list; # a; b c d
set new "b;"
lset list 1 $new
chan puts stdout $list; # {a;} {b;} c d
chan puts stdout [lindex [lindex $list 1] 0]; # b;
chan puts stdout [lindex [lindex $list 3] 0]; # d
chan puts stdout [lindex $new 0]; # b;
I'm working with strings and breaking them into words
It is important to use split for this step, other than that, you should be fine using the so-produced list by appending to it. The various list commands will make sure that special characters (your ;) will be protected from being ill-interpreted.
From the Tcl perspective, there is no difference between a single-element list {a;} and an atomic string a;. To quote the Tclers' Wiki:
No program can tell the difference between the string "a" and the
one-element list "a", because the one-element list "a" is the string
"a".
Don't let the curly braces confuse you.

Issue with string match when having square brackets in the operands strings

This looks as expected:
set a 1
puts [string match $a $a]
>> 1
However I find this unexpected:
set b {[1]}
puts [string match $b $b]
>> 0
Can you help explain the above behaviour?
The pattern [1] is a bracket expression that matches the characters inside the brackets. In this case, the only string that will match the pattern is 1.
% set b {[1]}
[1]
% puts [string match $b $b]
0
% puts [string match $b "1"]
1
%
If you'd like to compare two strings to see if they are identical, use string equal ... instead.
If you are in a unix shell environment, man n string or man 3tcl string should bring up a manual page with details about the string command.

How to pass a tcl script a string and interpret it properly

I'm trying to modify a tcl script that pushes bitfiles onto fpgas using xilinx's xsct tool. Here's what it looks like:
connect hw_server TCP:127.0.0.1:3122
targets -set -filter {jtag_cable_name =~ "Digilent JTAG-HS2 21xxx" && name =~ "xc7*"}
fpga [lindex $argv 0]
after 100
targets -set -filter {jtag_cable_name =~ "Digilent JTAG-HS2 21xxx" && name =~ "Micro*"}
loadhw system.hdf
stop
dow [lindex $argv 1]
con -block
Now that I have multiple FPGAs, I'd like to make the jtag_cable_name an argument. I've tried this to no avail:
connect hw_server TCP:127.0.0.1:3121
targets -set -filter {jtag_cable_name =~ [eval [lindex[$argv 0]] && name =~ "xc7*"}
fpga [lindex $argv 1]
after 100
targets -set -filter {jtag_cable_name =~ [eval [lindex[$argv 0]] && name =~ "Micro*"}
loadhw system.hdf
stop
dow [lindex $argv 2]
con -block
the call to the .tcl script looks like:
load_fpga.tcl "Digilent JTAG-HS2 21xxx" my_bitfile.bit my_elf.elf
How can I correctly pass the string and keep it in quotes like the original is?
What you don't realise is that in tcl, {Hello World} is a string.
If you are familiar with languages like Perl or Ruby than you would be familiar with the concept of literal and interpolated strings. In tcl, there are three syntaxes for strings:
Anything that doesn't contain a whitespace (space, tab, newline) is a string. Also, whitespace may be escaped. An escaped whitespace is not considered whitespace. The following are strings:
hello
hello\ world
Anything grouped by " are interpolated strings. Interpolated strings may contain variables or commands which will be evaluated and substituted. The following strings all say "hello world":
set x hello
"hello world"
"$x world"
"[set x] world"
Anything grouped by {} are literal strings. Literal strings aren't interpolated. That is, no variables or commands are evaluated. The following are literal strings:
{hello world} ;# hello world
{$x world} ;# $x world
There are two things you can do to get what you want
Replace {} with "". This will simply make the literal string into an interpolated string:
"jtag_cable_name =~ [eval [lindex[$argv 0]] && name =~ \"xc7*\""
note how you need to escape the " inside the string.
Use the subst command. The subst command performs substitutions on a string:
[subst {jtag_cable_name =~ [eval [lindex[$argv 0]] && name =~ "xc7*"}]
The small advantage of this is you don't need to escape the ". Read the manual page for subst. It's highly flexible allowing you to select what type of things you want to substitute. For example it allows you to evaluate only commands but not the $ syntax.
You probably want:
set jtag_cable_name [lindex $argv 0]
# ...
targets -set -filter [format {jtag_cable_name =~ "%s" && name =~ "xc7*"} $jtag_cable_name]
You can assign all the command line args like this:
lassign $argv jtag_cable_name bit_filename elf_filename