To check if specific string "PIN" present in the line or not -Tcl - tcl

The file contains a set of data I want to compare the "PIN" on each line if a string is present store the entire line into the new variable
PIN i_hbmc_ieee1500_sel_wir
DIRECTION INPUT ;
USE SIGNAL ;
PORT
LAYER K3 ;
RECT 2090.163000;
END
END i_hbmc_ieee1500_sel_wir
PIN i_hbmc_ieee1501_sel_wir
DIRECTION INPUT ;
USE SIGNAL ;
PORT
LAYER K3 ;
RECT 2090.163000;
END
END i_hbmc_ieee1500_sel_wir wir
The output is getting 1, The code I tried is here, and the expecting outcome as
PIN i_hbmc_ieee1500_sel_wir
PIN i_hbmc_ieee1501_sel_wir
set filedata [open "osi_hbmp_top_briscm_1.txt" r]
set file1 [ read $filedata ]
puts [string compare "PIN" $file1]

string compare (lexographically) compares if two strings are the same. see the docs. So 1 is returned because the input is lexographically greater then the "PIN".
If you want to print the lines starting with "PIN", you first need to split the input into lines (separating by the end-line character \n and then match the beginning:
foreach line [split $file1 \n] {
if {[string match "PIN*" $line]} {
puts $line
}
}
this will return
PIN i_hbmc_ieee1500_sel_wir
PIN i_hbmc_ieee1501_sel_wir

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.

Replace same strings with swap difference?

To manipulate Strings in Tcl, we use the string command.
If you need to replace comma:
set value { 10.00 }
puts [string map -nocase { . , } $value]
# Return: 10,00
We can replace several strings:
set text "This is a replacement test text"
puts [string map -nocase { e E s S a A } $text]
# Returns: THIS IS A TEXT OF REPLACEMENT TEST
Of course, we can replace words:
set text "This is a replacement test text"
puts [string map -nocase {test TEST a {second}} $text]
# Returns: This is the second replacement TEST text.
So far so good!
But one question that does not want to be silent is .. How to replace more than one identical occurrence in the sentence, giving a DIFFERENT substitution for each of them?
For example:
set time {10:02:12}
puts [string map -nocase { { : +} {: =} } $time]
I would like this result: 10 + 02 = 12
proc seqmap {str match args} {
set rc $str
foreach l [lreverse [regexp -all -indices -inline ***=$match $str]] \
replacement [lreverse $args] {
set rc [string replace $rc {*}$l $replacement]
}
return $rc
}
seqmap 10:02:12 : { + } { = }
=> 10 + 02 = 12
I'm using lreverse in case the replacement has a different length than the string it replaces. The indices would be off if the replacements were done from left to right.
The ***= is used to avoid special treatment of wildcard characters in the match string.
Of course, things get a lot more complicated if you want to handle the case where the number of occurrences doesn't match the number of provided substitutions. And even more if you want to replace several different strings.
This version handles the complications mentioned above:
proc seqmap {map str} {
# Transform the map into a dict with each key containing a list of replacements
set mapdict {}
foreach {s r} $map {dict lappend mapdict $s $r}
# Build a map where each key maps to a unique tag
# At the same time build a dict that maps our tags to the replacements
# First map the chosen tag character in case it is present in the string
set newmap {# #00}
set mapdict [dict map {s r} $mapdict {
lappend newmap $s [set s [format #%02d [incr num]]]
set r
}]
# Add the tag character to the dict so it can be mapped back
dict set mapdict #00 #
# Map the tags into the string
set rc [string map $newmap $str]
# Locate the positions where the tags ended up
set match [regexp -all -indices -inline {#\d\d} $rc]
# Create a list of replacements matching the tags
set replace [lmap l $match {
# Extract the tag
set t [string range $rc {*}$l]
# Obtain a replacement for this tag
set s [lassign [dict get $mapdict $t] r]
# Return the used replacement to the end of the list
dict set mapdict $t [linsert $s end $r]
# Add the replacement to the list
set r
}]
# Walk the two lists in reverse order, replacing the tags with the selected replacements
foreach l [lreverse $match] r [lreverse $replace] {
set rc [string replace $rc {*}$l $r]
}
# Done
return $rc
}
You call it just like you would string map, so with a key-value mapping and the string to perform the replacements on. Any duplicated keys specify the subsequent values to be substituted for each occurrence of the key. When the list is exhausted it starts over from the beginning.
So puts [seqmap {: + : = : *} 10:02:12] => 10+02=12
And puts [seqmap {: + : =} 10:02:12:04:16] => 10+02=12+04=16
As presented, the command can handle up to 99 unique keys. But it can easily be updated if more are needed.

How to split string by numerics

I havetried to split but still failed.
set strdata "34a64323R6662w0332665323020346t534r66662v43037333444533053534a64323R6662w0332665323020346t534r66662v430373334445330535"
puts [split $strdata "3334445330535"] ;#<---- this command does not work
The result needed as below:
{34a64323R6662w0332665323020346t534r66662v43037} {34a64323R6662w0332665323020346t534r66662v43037}
The split command's optional second argument is interpreted as a set of characters to split on, so it really isn't going to do what you want. However, there are other approaches. One of the simpler methods of doing what you want is to use string map to convert the character sequence into a character that isn't in the input data (Unicode is full of those!) and then split on that:
set strdata "34a64323R6662w0332665323020346t534r66662v43037333444533053534a64323R6662w0332665323020346t534r66662v430373334445330535"
set splitterm "3334445330535"
set items [split [string map [list $splitterm "\uFFFF"] $strdata] "\uFFFF"]
foreach i $items {
puts "==> $i"
}
# ==> 34a64323R6662w0332665323020346t534r66662v43037
# ==> 34a64323R6662w0332665323020346t534r66662v43037
# ==> {}
Note that there is a {} (i.e., an empty-string list element) at the end because that's the string that came after the last split element. If you don't want that, add a string trimright between the string map and the split:
# Doing this in steps because the line is a bit long otherwise
set mapped [string map [list $splitterm "\uFFFF"] $strdata]
set trimmed [string trimright $mapped "\uFFFF"]
set items [split $trimmed "\uFFFF"]
The split command doesn't work like that, see the documentation.
Try making the data string into a list like this:
regsub -all 3334445330535 $strdata " "
i.e. replacing the delimiter with a space.
Documentation:
regsub,
split

How to delete a part of the text file if a pattern is found matching using tcl?

How can I remove a part of the text file if the pattern I am searching is matched?
eg:
pg_pin (VSS) {
direction : inout;
pg_type : primary_ground;
related_bias_pin : "VBN";
voltage_name : "VSS";
}
leakage_power () {
value : 0;
when : "A1&A2&X";
**related_pg_pin** : VBN;
}
My pattern is related_pg_pin. If this pattern is found i want to remove that particular section(starting from leakage power () { till the closing bracket}).
proc getSection f {
set section ""
set inSection false
while {[gets $f line] >= 0} {
if {$inSection} {
append section $line\n
# find the end of the section (a single right brace, #x7d)
if {[string match \x7d [string trim $line]]} {
return $section
}
} else {
# find the beginning of the section, with a left brace (#x7b) at the end
if {[string match *\x7b [string trim $line]]} {
append section $line\n
set inSection true
}
}
}
return
}
set f [open data.txt]
set g [open output.txt w]
set section [getSection $f]
while {$section ne {}} {
if {![regexp related_pg_pin $section]} {
puts $g $section
}
set section [getSection $f]
}
close $f
close $g
Starting with the last paragraph of the code, we open a file for reading (through the channel $f) and then get a section. (The procedure to get a section is a little bit convoluted, so it goes into a command procedure to be out of the way.) As long as non-empty sections keep coming, we check if the pattern occurs: if not, we print the section to the output file through the channel $g. Then we get the next section and go to the next iteration.
To get a section, first assume we haven't yet seen any part of a section. Then we keep reading lines until the end of the file is found. If a line ending with a left brace is found, we add it to the section and take a note that we are now in a section. From then on, we add every line to the section. If a line consisting of a single right brace is found, we quit the procedure and deliver the section to the caller.
Documentation:
! (operator),
>= (operator),
append,
close,
gets,
if,
ne (operator),
open,
proc,
puts,
regexp,
return,
set,
string,
while,
Syntax of Tcl regular expressions
Syntax of Tcl string matching:
* matches a sequence of zero or more characters
? matches a single character
[chars] matches a single character in the set given by chars (^ does not negate; a range can be given as a-z)
\x matches the character x, even if that character is special (one of *?[]\)
Here's a "clever" way to do it:
proc unknown args {
set body [lindex $args end]
if {[string first "related_pg_pin" $body] == -1} {puts $args}
}
source file.txt
Your data file appears to be Tcl-syntax-compatible, so execute it like a Tcl file, and for unknown commands, check to see if the last argument of the "command" contains the string you want to avoid.
This is clearly insanely risky, but it's fun.

TCL string match from file

I am trying to find a string in a file in TCL. Using the wish console, I get a successful match between two strings. When I read a string from a file and match it to its exact copy, it fails. I can see in Eclipse that the variables contain exactly the same string...that is unless there are invisible characters trailing. The following code never returns 1, even when the variables contain exactly the same strings.
set fileId [open $::InputFile "r"]
set file_data [read $fileId]
# Process data file
set data [split $file_data "\n"]
#search for string
foreach line $data {
set x $::StringToFind
set y $line
set z [string match x y]
puts $z
if [ string match $::StringToFind line ] {
return 1
}
}
You need to use the dollar sign on the line variable to get its value:
if [ string match $::StringToFind $line ] {
Also, it is a good practice to quote the condition of the if command:
if {[string match $::StringToFind $line]} {