Inserting single curly braces to Tcl list elements - tcl

I have a report file having multiple lines in this form:
str1 num1 num2 ... numN str2
Given that (N) is not the same across lines. These numbers represent coordinates, so I need to enclose each point with curly braces to be:
{num1 num2} {num3 num4} and so on...
I have tried this piece of code:
set file_r [open file.rpt r]
set lines [split [read $file_r] "\n"]
close $file_r
foreach line $lines {
set items [split $line]
set str1 [lindex $items 0]
set str2 [lindex $items [expr [llength $items] - 1]]
set box [lrange $items 1 [expr [llength $items] - 2]]
foreach coord $box {
set index [lsearch $box $coord]
set index_rem [expr $index % 2]
if {index_rem == 0} {
set box [lreplace $box $index $index "{$coord"]
} else {
set box [lreplace $box $index $index "$coord}"]
}
}
puts "box: $box"
}
This gives me a syntax error that a close-brace is missing. And if I try "\{$coord" the back-slash character gets typed in the $box.
Any ideas to overcome this?

There are a few things you could improve to have better and simpler Tcl style.
You usually don't need to use split to form a list from a line if the line is already space separated. Space separated strings can almost always be used directly in list commands.
The exceptions are when the string contains { or " characters.
lindex and lrange can take end and end-N arguments.
This plus Donal's comment to use lmap will result in this:
set file_r [open file.rpt r]
set lines [split [read $file_r] "\n"]
close $file_r
foreach line $lines {
set str1 [lindex $line 0]
set str2 [lindex $line end]
set numbers [lrange $line 1 end-1]
set boxes [lmap {a b} $numbers {list $a $b}]
foreach box $boxes {
puts "box: {$box}"
}
}

Related

Get line in file

How to get string in line 3 from end line or maximum line [expr max_line - 3], i have write the code below but I cannot get by line 3 from end line or maximum line.
set idx 0
while {![eof $flopen]} {
gets $flopen line
puts $line
set vlist [split $line " "]
set vle [string trim [lindex $vlist 0]]
if {$vle == "STP"} {
set dtxid [string trim [lindex $value_list 1]]
set dtid [string trim [lindex $value_list 4]]
gets $flopen line
gets $flopen line
gets $flopen line
set line [join $line ","]
set tglist($idx) $dtxid
set gslist($idx) $dtid
set atblist($idx) $line
set data_end_from_max_line $datax ;# Can set the string here [expr $max_line - 3]
incr idx
}
}
When doing this sort of thing, the easiest approach (provided the data isn't too large, so no more than a couple of hundred megabytes) is to load it all in and process it inside Tcl as a list of lines.
set lines [split [read $flopen] "\n"]
set particularLine [lindex $lines end-3]

How to parse a text file in tcl using separators?

I have a text file of the format
35|46
36|49
37|51
38|22
40|1
39|36
41|4
I have to read the file into an array across the separator "|" where left side will be the key of the array and right side will be the value.
I have used the following code
foreach {line} [split [read $lFile] \n] {
#puts $line
foreach {lStr} [split $line |] {
if { $lStr!="" } {
set lPartNumber [lindex $lStr 0]
set lNodeNumber [lindex $lStr 1]
set ::capPartsInterConnected::lMapPartNumberToNodeNumber($lPartNumber) $lNodeNumber
}
}
}
close $lFile
I am not able to read the left side of the separator "|". How to do it?
And similarly for this :
35|C:\AI\DESIGNS\SAMPLEDSN50\BENCH_WORKLIB.OLB|R
36|C:\AI\DESIGNS\SAMPLEDSN50\BENCH_WORKLIB.OLB|R
I need to assign all three strings in different variables
You are making mistake in the foreach where the result of split will be assigned to a loop variable lStr where it will contain only one value at a time causing the failure.
With lassign, this can be performed easily.
set fp [open input.txt r]
set data [split [read $fp] \n]
close $fp
foreach line $data {
if {$line eq {}} {
continue
}
lassign [split $line | ] key value
set result($key) $value
}
parray result
lassign [split "35|C:\\AI\\DESIGNS\\SAMPLEDSN50\\BENCH_WORKLIB.OLB|R" |] num userDir name
puts "num : $num"
puts "userDir : $userDir"
puts "name : $name"

split a string at the 4th occurence of a comma in tcl

I have a list
set list "abc,def,ghi,jkl,mno,pqr,stu,vwx"
Now I want to split this list on the 4th occurence of the comma.
I want the list to be divided into two lists:
A = abc,def,ghi,jkl
B = mno,pqr,stu,vwx
How about simply using the list operators to split and re-join:
puts [set A [join [lrange [split $list ,] 0 3] ,]]
puts [set B [join [lrange [split $list ,] 4 end] ,]]
Or, if you wanted to go the regexp route, do it in one operation:
regexp -- {((?:\w+,){3}\w+),(.*)} $list --> A B
puts $A
puts $B
set list "abc,def,ghi,jkl,mno,pqr,stu,vwx"
regexp -- {(\w+,){3}\w+} $list A
regsub -- "${A}," $list {} B
puts $A
puts $B
A rather complicated example :)
set occurence 4
set slice_position 0
set list "abc,def,ghi,jkl,mno,pqr,stu,vwx"
for {set i 0} {$i < $occurence} {incr i} {
set slice_position [string first "," $list [expr $slice_position + 1]]
if {$slice_position == -1} {
break
}
}
puts [string range $list 0 $slice_position-1]
puts [string range $list $slice_position+1 end]

How to split string and store in list via TCL

Is there a way to split strings and save in a list ?
How to split string and save in two list
For example, I have a string where I split several string with =:
a=1
b=2
c=3
d=4
and then I want to create two list like this [a,b,c,d] and [1,2,3,4]:
Following is a simple tcl code
set s "a=1\nb=2\nc=3\nd=4"
set s [split $s "\n"]
foreach e $s {
set e [split $e "="]
lappend l1 [lindex $e 0]
lappend l2 [lindex $e 1]
}
Now you have list l1 with [a b c d] and l2 has [1 2 3 4]
The simplest way is to read all the data in, split into lines, and then use regexp with each line to extract the pieces.
set f [open "theFile.txt"]
set lines [split [read $f] "\n"]
close $f
set keys [set values {}]
foreach line $lines {
if {[regexp {^([^=]*)=(.*)$} $line -> key value]} {
lappend keys $key
lappend values $value
} else {
# No '=' in the line!!!
}
}
# keys in $keys, values in $values
puts "keys = \[[join $keys ,]\]"
puts "values = \[[join $values ,]\]"
Run that (assuming that the filename is right) and you'll get output like:
keys = [a,b,c,d]
values = [1,2,3,4]
Collecting two lists like that might not be the best thing to do with such stuff. Often, it is better to instead to store in an array:
# Guarded by that [regexp] inside the foreach
set myArray($key) $value
Like that, you can do lookups by name rather than having to manually search. Assuming that keys are unique and order doesn't matter.
A simple way might be using a loop:
% set lines "a=1\nb=2\nc=3\nd=4"
a=1
b=2
c=3
d=4
% set expressionList [split $lines "\n"]
a=1 b=2 c=3 d=4
% set var [list]
% set val [list]
% foreach i $expressionList {
set variable [lindex [split $i "="] 0]
set value [lindex [split $i "="] 1]
lappend val $value
lappend var $variable
}
% puts $var
a b c d
% puts $val
1 2 3 4
If you don't mind a regex, you might try something like this:
% set lines "a=1\nb=2\nc=3\nd=4"
a=1
b=2
c=3
d=4
% set var [regexp -inline -lineanchor -all -- {^[^=\n\r]+} $lines]
a b c d
% set val [regexp -inline -lineanchor -all -- {[^=\n\r]+$} $lines]
1 2 3 4
If replacing the equals sign characters in $data with blanks always leaves a proper, even-valued list (as in the example) it can be done a lot simpler:
set dict [string map {= { }} $data]
set keys [dict keys $dict]
set values [dict values $dict]
Documentation: dict, set, string
Let say your strings placed in file abc.txt in the following order
a=1
b=2
c=3
d=4
You need to create 2 lists, one for numbers and one for characters:
set number_list [list]
set char_list [list]
set fh [open "abc.txt" "r"]
while {[gets $fh line] != -1} {
regexp -- {(\S+)=(\S+)} $line foo char number
lappend char_list $char
lappend number_list $number
}
close $fh
puts $char_list
puts $number_list
This is pretty old, but I would actually go about it differently... Something like the following, considering that the string is [a=1\nb=1\n ... etc.] with variable name "str":
# determine num entries in string
set max [llength $str]
#create new strings (alph & num) based on split string
set i 0
set str [split $str \n]
set alph []
set num []
while {$i < $max} {
set alph "$alph [lindex [split [lindex $str $i] "="] 0]
set num "$num [lindex [split [lindex $str $i] "="] 1]
incr i}
Maybe just personal preference, but seems simplest to me; code was not tested, but it's similar to something I was just working on.

splitting input line with varying formats in tcl with

Good afternoon,
I am attempting to write a tcl script which given the input file
input hreadyin;
input wire htrans;
input wire [7:0] haddr;
output logic [31:0] hrdata;
output hreadyout;
will produce
hreadyin(hreadyin),
htrans(htrans),
haddr(haddr[7:0]),
hrdata(hrdata[31:0]),
hready(hreadyout)
In other words, the format is:
<input/output> <wire/logic optional> <width, optional> <paramName>;
with the number of whitespaces unrestricted between each of them.
I have no problem reading from the input file and was able to put each line in a $line element. Now I have been trying things like:
set param0 [split $line "input"]
set param1 [lindex $param0 1]
But since not all lines have "input" line in them i am unable to get the elements i want (the name and the width if it exists).
Is there another command in tcl capable for doing this kind of parsing?
The regexp command is useful to find words separated by arbitrary whitespace:
while {[gets $fh line] != -1} {
# get all whitespace-separated words in the line, ignoring the semi-colon
set i [string first ";" $line]
set fields [regexp -inline -all {\S+} [string range $line 0 $i-1]]
switch -exact -- [llength $fields] {
2 - 3 {
set name [lindex $fields end]
puts [format "%s(%s)," $name $name]
}
4 {
lassign $fields - - width name
puts [format "%s(%s%s)," $name $name $width]
}
}
}
I think you should look at something like
# Compress all multiple spaces to single spaces
set compressedLine [resgub " +" $line " "]
set items [split [string range $compressedLine 0 end-1] $compressedLine " "]
switch [llength $items] {
2 {
# Handle case where neither wire/logic nor width is specificed
set inputOutput [lindex $items 0]
set paramName [lindex $items 1]
.
.
.
}
4 {
# Handle case where both wire/logic and width are specified
set inputOutput [lindex $items 0]
set wireLogic [lindex $items 1]
set width [lindex $items 2]
set paramName [lindex $items 3]
.
.
.
}
default {
# Don't know how to handle other cases - add them in if you know
puts stderr "Can't handle $line
}
}
I hope it's not legal to have exactly one of wire/logic and width specified - you'd need to work hard to determine which is which.
(Note the [string range...] fiddle to discard the semicolon at the end of the line)
Or if you can write up a regex that catches the right data, you can do this with this:
set data [open "file.txt" r]
set output [open "output.txt" w]
while {[gets $data line] != -1} {
regexp -- {(\[\d+:\d+\])?\s*(\w+);} $line - width params
puts $output "$params\($params$width\),"
}
close $data
close $output
This one will also print the comma you have inserted in your expected output, but will insert it in the last line as well so you get:
hreadyin(hreadyin),
htrans(htrans),
haddr(haddr[7:0]),
hrdata(hrdata[31:0]),
hready(hreadyout),
If you don't want it and the file is not too large (apparently the limit is 2147483672 bytes for a list, which I'm gonna use), you could use a group like this:
set data [open "file.txt" r]
set output [open "output.txt" w]
set listing "" #Empty list
while {[gets $data line] != -1} {
regexp -- {(\[\d+:\d+\])?\s*(\w+);} $line - width params
lappend listing "$params\($params$width\)" #Appending to list instead
}
puts $output [join $listing ",\n"] #Join all in a single go
close $data
close $output