How to write back the variables in a tcl readable format to the file intelligently using tcl in the same format - tcl

This code has a class named class1 and methods set, get, load, save
# Class1 definition
oo::class create class1 {
variable dataArr
method set {key value} {
set dataArr($key) $value
}
method get {key} {
if {[info exist dataArr($key)]} {
return $dataArr($key)
}
}
method load {} {
set fp [open /home/karthikc/data.tcl r]
set file_data [read $fp]
puts $file_data
eval $file_data
close $fp
}
method save {{newFilePath ""}} {
if [info exists filePath] {
set tmpFP $filePath
}
if {$newFilePath ne ""} {
set tmpFP $newFilePath
}
if ![info exists tmpFP] {
puts"neither newFilePath argument is passed nor filePath variable is present"
return 0
}
try {
set fhandle [open $tmpFP w]
if ![info exists dataArr] {
puts "dataArr variable doesn't exist in the object [self]"
return 0
}
foreach key [array names dataArr] {
set kvPair [list $key $dataArr($key)]
lappend dataLst $kvPair
puts $fhandle "my set $key $dataArr($key)"
puts "my set $key $dataArr($key)"
}
set filePath $tmpFP
puts "dictionary is successfully saved in the file path"
} on error {result opts} {
puts $result
puts "Return options Directory"
puts $opts
return 0
} finally {
if [info exist fhandle] {
close $fhandle
}
}
return 1
}
}
Which I use like this:
# create object instance
set obj [class1 new]
# call load method
$obj load
# call save method
$obj save /home/karthikc/data.tcl
my data.tcl is
my set key1 value1
my set key2 value2
my set key3 [list valueA valueB valueC]
my set key4 [list valueX [list valueY valueZ]]
I want to write back to same format or some other list of lists

Suggestions for improvement
You can simplify your SERIALIZER, and render it more robust along the way.
First, don't serialize the object's state into a script, but a literal map (associative array or dict in Tcl). And read it as such:
data.tcl could look like:
key3 {valueA valueB valueC}
key4 {valueX {valueY valueZ}}
key1 value1
key2 value2
Your load method could use array set to read this directly:
method load {} {
set fp [open /tmp/data.tcl r]
set file_data [read $fp]
array set dataArr $file_data
close $fp
}
Your save method can directly use array get and produce formatted output:
method save2 {{newFilePath ""}} {
if {[array exists dataArr]} {
set fhandle [open $newFilePath w]
set out ""
foreach {k v} [array get dataArr] {
append out $k " " [list $v] \n
}
puts $fhandle $out
close $fhandle
}
}
Key to the idea is to avoid eval, and, therefore, code injection. And the serialization format matches 1:1 first-class Tcl data structures.
Improving your question
Allow me to say, your question is not a proper question. It does not state a problem, one has to read between the lines and snippets to sense what you are after. Also, the code example should be reduced to a minimum, to demonstrate your perceived problem. It is not helpful to paste your entire code work.

I changed the format of storing the lists
from
my set key1 value1
my set key2 value2
my set key3 [list valueA valueB valueC]
my set key4 [list valueX [list valueY valueZ]]
to
my set key1 value1
my set key2 value2
my set key3 {valueA valueB valueC}
my set key4 {valueX {valueY valueZ}}
and in save method I changed
puts $fhandle "my set $key $dataArr($key)"
to
puts $fhandle [list my set $key $dataArr($key)]
And these changes serve the purpose.

Related

Tcl code to fetch pin details and compare with another file pins

I have two files and I am comparing specific lines between two files using the def function. python and I am trying to write same code on tcl
The file data is given below
PIN i_hbmc_ieee1500_sel_wir
DIRECTION INPUT ;
USE SIGNAL ;
PORT
LAYER K3 ;
RECT 2090.163000 3265.856000 2090.476000 3265.920000 ;
END
END i_hbmc_ieee1500_sel_wir
PIN i_hbmc_ieee1500_cap_wr
DIRECTION INPUT ;
USE SIGNAL ;
PORT
LAYER K3 ;
RECT 2090.163000 3265.984000 2090.476000 3266.048000 ;
END
END i_hbmc_ieee1500_cap_wr
PIN i_hbmc_ieee1500_shft_wr
DIRECTION INPUT ;
USE SIGNAL ;
PORT
LAYER K3 ;
RECT 2090.163000 3265.728000 2090.476000 3265.792000 ;
END
END i_hbmc_ieee1500_shft_wr
The python code to fetch pin details of both files and compare between files
def readPinFile(filename):
result = None
with open(filename, "r") as file:
result = {}
lastPin = None
for line in file:
lines = line.strip()
if lines[:3] == "PIN":
lastPin = lines.split(" ")[1]
result[lastPin] = {"LAYER": None, "RECT": None}
if lines[:5] == "LAYER":
result[lastPin]["LAYER"] = lines.split(" ")[1]
if lines[:4] == "RECT":
result[lastPin]["RECT"] = lines.split(" ")
return result
pin_of_file1 = readPinFile("osi_hbmp_top_briscm_1.lef") #lef file1
pin_of_file2 = readPinFile("osi_hbmp_top_briscm_2.lef")#lef file2
with open("file04.txt", "r+") as output_file4: #compare same pins with layer and location
for pin, pin_data in pin_of_file1.items():
if pin in pin_of_file2:
if pin_of_file2[pin]["LAYER"] == pin_data["LAYER"] and pin_of_file2[pin]["RECT"] == pin_data["RECT"]:
output_file4.write(pin + "\n\n")
The TCL code I tried to get the same output
proc fileinput {filename} {
set filedata [open filename r]
set file1 [ read $filedata ]
foreach line [split $file1 \n] {
set pindata { PIN { LAYER {} RECT {} }}
if {[string match *PIN* $line]} {
dict lappend pindata PIN $line
}
if {[string match *LAYER* $line]} {
dict lappend pindata PIN {LAYER{$line}}
}
if {[string match *RECT* $line]} {
dict lappend pindata PIN {RECT{$line}}
}
}
return $pindata
}
set fileinput1 [fileinput osi_hbmp_top_briscm_1.txt]
set fileinput2 [fileinput osi_hbmp_top_briscm_2.txt]
In tcl I am trying to write comparing between the pins section (last 4-5 lines on python code), but I am stuck in the middle. I am fully confused to continue this code. can anyone help me to complete this code(mainly last 2 lines of python code)
foreach $pin, $pin_data [gets $fileinput1]
if{[string match $pin $fileinput2]}
This is the code I tried
Your code is using a proc called fileinput but you didn't include the proc definition. It actually looks like you are including the body of the proc, you didn't include the proc command at the beginning.
I will assume you want to do this (I also changed how the pindata dictionary is set)
proc fileinput {filename} {
set filedata [open $filename r]
set file1 [ read $filedata ]
close $filedata
set pindata [dict create]
foreach line [split $file1 \n] {
if {[string match "PIN*" $line]} {
set pin [lindex $line 1]
}
if {[string match "LAYER*" $line]} {
set layer [lindex $line 1]
dict lappend pindata $pin layer $layer
}
if {[string match "RECT*" $line]} {
set rect [lrange $line 1 4]
dict lappend pindata $pin rect $rect
}
}
return $pindata
}
Now the proc returns a dictionary with a top key set to the pin name and nested keys called "layer" and "rect".
To compare the pin layer of two different files:
# Parse each file and return a dict
set pin_data1 [fileinput osi_hbmp_top_briscm_1.txt]
set pin_data2 [fileinput osi_hbmp_top_briscm_2.txt]
# Iterate over the keys and compare layers:
foreach pin_name [dict keys $pin_data1] {
set layer1 [dict get $pin_data1 $pin_name layer]
# Check that the pin_name is in the second file
if {![dict exists $pin_data2 $pin_name]} {
puts "$pin_name exists in pin_data1 but not pin_data2"
continue
}
# If we get this far, then $pin_name appears in both files.
set layer2 [dict get $pin_data2 $pin_name layer]
if {$layer1 ne $layer2} {
puts "Layer mismatch for $pin_name:"
puts " 1: $layer1"
puts " 2: $layer2"
}
}
By the way, your example input file is incomplete. There is an END for a pin name that was never declared earlier.

Tcl: How to replace variable string?

Input file is a tcl script and it looks like:
set PATH /user/abc/path
set PATH2 /user/abc/path2
...
read_verilog ${PATH}/src/vlog/code_1.v
read_verilog $PATH/src/vlog/code_2.v
read_vhdl ${PATH2}/src/vhd/code_3.vh
read_vhdl $PATH2/src/vhd/code_4.vh
[other commands ...]
Need to check if the source file is exist and print out none-exist files.
If none of the file is exist, the output looks like:
read_verilog ${PATH}/src/vlog/code_1.v
read_verilog $PATH/src/vlog/code_2.v
read_vhdl ${PATH2}/src/vhd/code_3.vh
read_vhdl $PATH2/src/vhd/code_4.vh
And below is my script:
#!/usr/bin/tclsh
set input_file "input.tcl"
set input_fpt [open $input_file r]
set input_lines_all [read $input_fpt]
set input_lines [split $input_lines_all "\n"]
set PATH /user/abc/PATH
set PATH /user/dgc/PATH2
foreach line $input_lines {
if { [string match "read_verilog *" $line] || [string match "read_vhdl*" $line] } {
regexp {[read_verilog read_vhdl] (.*)} $line matched file
if { [string match {*[{P]AT[H}]*} $file] } {
set abs_file [string map {${PATH} /user/abc/PATH} $file]
} elseif { [string match "*PATH2*" $file] } {
set abs_file [string map {${PATH2} /user/abc/PATH2} $file]
} else {
set abs_file $file
}
if { ![file exists $abs_file] } {
puts $line
}
}
}
My script can't check $PATH and not sure if there is a more efficient way to do the job.
The simplest way of doing just the substitutions you want is with the string map command. Build up the map piece by piece first, then apply it to your string.
set map {}
lappend map {$PATH} $PATH
lappend map {${PATH}} $PATH
lappend map {$PATH2} $PATH2
lappend map {${PATH2}} $PATH2
set modified [string map $map $inputString]
You can apply the map as many times as you want once you have built it, and transform your data either a line at a time or all in one go. However, you might be better off just evaluating the file as a Tcl script. That can be an incredibly useful approach to some types of parsing (especially when used in conjunction with a safe interpreter) if the input is suitable, which yours appears to be.

How to extract against glob in tcl script?

I have set of files store into one variable and pass that variable to get duplicate and unique value in respective of variable A.
set A "232 234 234 234"
set a 1
set files_name "/usr/test/a_232.txt /usr/test/a1_234.txt /usr/test/a2_234.txt /usr/test/a3_234.txt"
foreach j [split $A " "] {
incr count($j)
}
foreach key [array names count] {
if {$count($key) == 1} {
set file_name1 [glob -type f $file_name {$key} ]
} else {
set file_name2 [glob -type f $file_name {$key} ]
}
}
Whenever I'm executing above code I got below error
no files matched glob patterns "/usr/test/a_232.txt /usr/test/a1_234.txt /usr/test/a2_234.txt /usr/test/a3_234.txt"
while executing
"glob -type f $file_list {$key} "
Result should be like this
file_name1 : /usr/test/a_232.txt
file_name2 :/usr/test/a1_234.txt /usr/test/a2_234.txt /usr/test/a3_234.txt
I think what you want to do is something like this:
set A "232 234 234 234"
set files_name "/usr/test/a_232.txt /usr/test/a1_234.txt /usr/test/a2_234.txt /usr/test/a3_234.txt"
unset -nocomplain count
foreach j [split $A] {
incr count($j)
}
foreach key [array names count] {
if {$count($key) == 1} {
set file_name1 [lsearch -regexp -inline $files_name $key]
} else {
set file_name2 [lsearch -regexp -inline -all $files_name $key]
}
}
Note: file_name2 will have as many names as there are matches for the key in the file names list, which may be more or less than there are occurrences of that key in A.
The glob command searches for file names in the file system, not in a list of files.
Documentation: array, foreach, if, incr, lsearch, split, unset

Redirecting the "parray" output to a file in tcl

I have an array in tcl.
For example:
set a(1) "First element"
set a(2) "second element"
parray a
parray a displays output as
a(1) = "First element"
a(2) = "second element"
Is it possible to redirect the parray output to a file?
The parray command can't be redirected. It's a simple-minded procedure that is too stupid to be redirected. But it's source code isn't very long; in fact, it's short enough that I'll just paste it here (it's under the Tcl license):
proc parray {a {pattern *}} {
upvar 1 $a array
if {![array exists array]} {
return -code error "\"$a\" isn't an array"
}
set maxl 0
set names [lsort [array names array $pattern]]
foreach name $names {
if {[string length $name] > $maxl} {
set maxl [string length $name]
}
}
set maxl [expr {$maxl + [string length $a] + 2}]
foreach name $names {
set nameString [format %s(%s) $a $name]
puts stdout [format "%-*s = %s" $maxl $nameString $array($name)]
}
}
Redirecting it (hint: change the stdout for something obtained from open … a, and don't forget to close it afterwards) should be a simple exercise.
This builds on the answers by Dinesh and Donal Fellows: You could adapt the code of parray automatically, like this:
auto_load parray
proc printArray {a {pattern *} {channel stdout}} \
[string map {stdout $channel} [info body parray]]
This gives you a new proc printArray with an optional channel argument.

tcl set list of arrays produce duplicates

I'm producing a TCL procedure that will return a list of arrays of devices under a switch. The definition is an XML file that is read. The resulting lists of XML entries are parsed using a recursive procedure and the device attributes are placed in an array.
Each array is then placed in a list and reflected back to the caller. My problem is that when I print out the list of devices, the last device added to the list is printed out each time. The contents of the list is all duplicates.
Note: I'm using the excellent proc, 'xml2list' that was found here. I'm sorry, I forgot who submitted this.
The following code illustrates the problem:
source C:/src/tcl/xml2list.tcl
# Read and parse XML file
set fh [open C:/data/tcl/testfile.xml r]
set myxml [read $fh]
set mylist [xml2list $myxml]
array set mydevice {}
proc devicesByName { name thelist list_to_fill} {
global mydevice
global set found_sw 0
upvar $list_to_fill device_arr
foreach switch [lindex $thelist 2] {
set atts [lindex $switch 1]
if { [lindex $switch 0] == "Switch" } {
if { $name == [lindex $atts 3] } {
set found_sw 1
puts "==== Found Switch: $name ===="
} else {
set found_sw 0
}
} elseif { $found_sw == 1 && [string length [lindex $atts 3]] > 0 } {
set mydevice(hdr) [lindex $switch 0]
set mydevice(port) [lindex $atts 1]
set mydevice(name) [lindex $atts 3]
set mydevice(type) [lindex $atts 5]
puts "Device Found: $mydevice(name)"
set text [lindex $switch 2]
set mydevice(ip) [lindex [lindex $text 0] 1]
lappend device_arr mydevice
}
devicesByName $name $switch device_arr
}
}
#--- Call proc here
# set a local array var and send to the proc
set device_arr {}
devicesByName "Switch1" $mylist device_arr
# read out the contents of the list of arrays
for {set i 0} {$i<[llength $device_arr]} {incr i} {
upvar #0 [lindex $device_arr $i] temp
if {[array exists temp]} {
puts "\[$i\] Device: $temp(name)-$temp(ip)"
}
}
The XML file is here:
<Topology>
<Switch ports="48" name="Switch1" ip="10.1.1.3">
<Device port="1" name="RHEL53-Complete1" type="host">10.1.1.10</Device>
<Device port="2" name="Windows-Complete1" type="host">10.1.2.11</Device>
<Device port="3" name="Solaris-Complete1" type="host">10.1.2.12</Device>
</Switch>
<Switch ports="36" name="Switch2" ip="10.1.1.4">
<Device port="1" name="Windows-Complete2" type="host">10.1.3.10</Device>
</Switch>
<Router ports="24" name="Router1" ip="10.1.1.2">
<Device port="1" name="Switch1" type="switch">10.1.1.3</Device>
<Device port="2" name="Switch2" type="switch">10.1.1.4</Device>
</Router>
</Topology>
If my code blocks look bad, please excuse that. I followed the directions as I read them, but it didn't look correct. I could not fix it, so just posted anyway.
Thanks in advance...
Arrays in tcl are not values. Therefore they don't behave like regular variables. They are in fact something special like filehandles or sockets.
You cannot assign an array to a list like that. Doing:
lappend device_arr mydevice
simply appends the string "mydevice" to the list device_arr. That string happens to be the name of a global variable so that string may be used later to access that global variable.
To build up a key-value data structure what you want is a dict. You can think of a dict as a special list that has even numbers of elements in the format: {key value key value}. In fact, this data structure works even on very old versions of tcl before the introduction of dicts because the foreach loop in tcl can be used to process key-value pairs.
So what you want is to create a new $mydevice dict each loop and use [dict set] to assign the values.
Alternatively you can keep most of your code and change your lappend to:
lappend device_arr [array get mydevice]
This works because [array get] returns a key-value list which can be treated as a dict. You can later access the data using the dict command.
Array variables can't be used as values. To put the contents of one into a list element, send it to a proc, write it to a file etc, convert it to list form (key, value, key, value...) with array get.
lappend device_arr [array get mydevice]
To use it later, write the list back to an array with array set.
foreach device_l $device_arr {
#array unset device
array set device $device_l
puts "$device(name)-$device(ip)"
}
Note that array set doesn't erase the old keys in the destination array, so if you use it in a loop and the key names aren't always the same, you need to clear the array every iteration.
You can store this information in two ways using arrays . First is as a multi-dimensional array, in this case a three dimensional array and the second is a one dimensional array storing a list that can be converted easily to an array later for accessing data at a later time.
For the 3d array the key would be Switch Name,device_port,dataname you would change your erroneous temporary myDevice and lappend code to
# attr is a list of { attributename1 value1 ... attributenameN valueN}
array set temp $attr
set port $temp(port)
set text [lindex $switch 2]
set ip [lindex [lindex $text 0] 1]
# name already set to "Switch1" etc
foreach f [array names temp ] {
set device_arr($name,$port,$f) $temp($f)
}
set device_arr($name,$port,ip) $ip
array unset temp
this code results in the following ( when parray device_arr
parray device_arr
device_arr(Switch1,1,name) "Switch1"
device_arr(Switch1,1,port) 1
device_arr(Switch1,1,type) "RedHat .."
device_arr(Switch1,1,ip) 10..
device_arr(Switch1,2,name) "Switch1"
device_arr(Switch1,2,port) 1
device_arr(Switch1,2,type) "RedHat .."
device_arr(Switch1,2,ip) 10..
...
device_arr(Switch2,1,name) "Switch1"
device_arr(Switch2,1,port) 1
device_arr(Switch2,1,type) "Windows Complete"
device_arr(Switch2,1,ip) 10..
....
to find ip of Switch1 port2 you would:
puts "the ip of Switch1 port 2 is $device_arr(Switch1,2,ip)"
Note lots of data duplication but you can access all data directly without having to go to an intermediate step to get to the data as in the next scheme
# attr is a list of { attributename1 value1 ... attributenameN valueN}
set data $attr
array set temp $attr
set text [lindex $switch 2]
set ip [lindex [lindex $text 0] 1]
lappend data ip $ip
set key "$name,$temp(port)"
# name already set to "Switch1" etc
set device_arr($name,$port) $data
array unset temp
doing a parray device_arr gives:
device_arr(Switch1,1) { port "1" name "RHEL53-Complete1" type "host" ip 10.1.1.10 }
device_arr(Switch1,2) { port "2" name "Windows-Complete1" type "host" ip 10.1.2.11}
....
to find the ip of swtich1 port 2 you would
array set temp $device_array(Switch1,2)
puts "ip of device 2 is $temp(ip)"