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

(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

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"}

TCL lindex removes quotes from list element

I have a list where some elements are strings where I need them to be enclosed in quotation marks.
If I iterate over the list and puts each element to the terminal the output looks correct. However, if I lindex to a specific element which is enclosed in quote marks, the quote marks disappear.
Example code:
set myList [list "BUILD_PROJ \"I have quotes\""]
foreach element $myList {
puts [lindex $element 1]
puts [lindex $element]
}
Outputs:
I have quotes
BUILD_PROJ "I have quotes"
How do I get a specific lindexed element from a list to keep its quotation marks?
My bad, getting mixed up with separators. I think escaping the quotation marks was effectively giving me a list with a single element. Updated to this:
set myList [list "BUILD_PROJ" "\"I have quotes\""]
puts $myList
puts [lindex $myList 0]
puts [lindex $myList 1]
And now the output is as I expect:
BUILD_PROJ {"I have quotes"}
BUILD_PROJ
"I have quotes"

reading file with "[" and manipulation each line TCL

I have file with the below lines (file.list):
insert_buffer [get_ports { port }] BUFF1 -new_net net -new_cell cell
I'm reading the file with the below script (read.tcl):
#! /usr/local/bin/tclsh
foreach arg $argv {
set file [open $arg r]
set data [ read $file ]
foreach line [ split $data "\n" ] {
puts $line
set name [lindex $line [expr [lsearch -all $line "-new_cell"]+1]]
puts $name
}
close $file
}
while running the above script (read.tcl file.list) I get error since I have "[" in file.list and script think its a beginning of TCL command.
list element in braces followed by "]" instead of space
while executing
"lsearch -all $line "-new_cell""
("foreach" body line 5)
invoked from within
"foreach line [ split $data "\n" ] {
How can I read the file correctly and overcome the "[" symbol?
How can I read the file correctly and overcome the "[" symbol?
I don't really understand why you are doing what you are doing (processing one Tcl script by another), but you have to make sure that each line is a valid Tcl list before submitting it to lsearch.
lsearch -all [split $line] "-new_cell"
Only split will turn an arbitrary string (containing characters special to Tcl) into a valid Tcl list.
This is one of the few times in Tcl that you need to worry about what type of data you have. $line holds a string. Don't use list commands on strings because there's no guarantee that an arbitrary string is a well-formed list.
Do this:
set fields [split $line]
# don't use "-all" here: you want a single index, not a list of indices.
set idx [lsearch -exact $fields "-new_cell"]
if {$idx == -1} {
do something here if there's no -new_cell in the line
} else {
set name [lindex $fields $idx+1]
}
In order to apply a list operation on the variable, it has to be a valid list. The variable $line is not a valid list.
It is better to use regexp rather than lsearch
regexp -- {-new_cell\s+(\S+)} $x match value
puts $value
Output :
cell

How to escape special characters while using lsearch?

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 {[]}

Converting Columns in a List in Tcl Script

I want to convert a column of a file in to list using Tcl Script. I have a file names "input.dat" with the data in two columns as follows:
7 0
9 9
0 2
2 1
3 4
And I want to convert the first column into a list and I wrote the Tcl Script as follows:
set input [open "input.dat" r]
set data [read $input]
set values [list]
foreach line [split $data \n] {
lappend values [lindex [split $line " "] 0]
}
puts "$values"
close $input
The result shows as: 7 9 0 2 3 {} {}
Now, my question is what is these two extra "{}" and what is the error in my script because of that it's producing two extra "{}" and How can I solve this problem?
Can anybody help me?
Those empty braces indicate empty strings. The file you used most probably had a couple empty lines at the end.
You could avoid this situation by checking a line before lappending the first column to the list of values:
foreach line [split $data \n] {
# if the line is not equal to blank, then lappend it
if {$line ne ""} {
lappend values [lindex [split $line " "] 0]
}
}
You can also remove those empty strings after getting the result list, but it would mean you'll be having two loops. Still can be useful if you cannot help it.
For example, using lsearch to get all the values that are not blank (probably simplest in this situation):
set values [lsearch -all -inline -not $values ""]
Or lmap to achieve the same (a bit more complex IMO but gives more flexibility when you have more complex situations):
set values [lmap n $values {if {$n != ""} {set n}}]
The first {} is caused by the blank line after 3 4.
The second {} is caused by a blank line which indicates end of file.
If the last blank line is removed from the file, then there will be only one {}.
If the loop is then coded in the following way, then there will be no {}.
foreach line [split $data \n] {
if { $line eq "" } { break }
lappend values [lindex [split $line " "] 0]
}
#jerry has a better solution
Unless intermittent empty strings carry some meaning important to your program's task, you may also use a transformation from a Tcl list (with empty-string elements) to a string that prunes empty-string elements (at the ends, and in-between):
concat {*}[split $data "\n"]