In TCL Script generate specific data structure in the file - tcl

I am trying to generate this structure
eg:
type (bus0) {
base_type : array ;
data_type : bit ;
bit_width : 2;
bit_from : 1;
bit_to : 0;
downto : true ;
}
bus (BITS_CLK2QDLY) {
bus_type : bus0
In type (bus0) { where bus increment each time, bit_width, bit_from and bit_to values will updated from dictionary, where bit_width = bit_to + 1.
BITS_CLK2QDLY dictionary name.
In the code that I tried, here I completed till $keyword_numbers only I have doubts in the last section of the first two blocks to know the code
proc fileinput {filename} {
set filedata [open $filename "r"]
set file [read $filedata]
............
...........
return $data
}
set data [fileinput "rx_clkgen_tdl_ss_0.675v_m40c.lib"]
where the $data consist
pin ("BITS_CLK2QDLY[0]")
pin ("BITS_CLK2QDLY[1]")
pin ("BITS_DCC_MAIN[0]")
pin ("BITS_DCC_MAIN[1]")
pin ("BITS_DCC_MAIN[2]")
pin ("BITS_DCC_MAIN[3]")
pin ("BITS_DCC_MAIN[4]")
pin ("BITS_DCC_MAIN[5]")
pin ("BITS_DCC_MAIN[6]")
pin ("BITS_FIXDLY_MAIN[0]")
pin ("BITS_FIXDLY_MAIN[1]")
pin ("BITS_FIXDLY_MAIN[2]")
pin ("BITS_FIXDLY_MAIN[3]")
pin ("BITS_FIXDLY_MAIN[4]")
pin ("BITS_FIXDLY_MAIN[5]")
pin ("BITS_FIXDLY_MAIN[6]")
pin ("BITS_FIXDLY_MAIN[7]")
pin ("BITS_NDE_DLY[0]")
pin ("BITS_NDE_DLY[1]")
pin ("BITS_NDE_DLY[2]")
pin ("BITS_NDE_DLY[3]")
pin ("BITS_NDE_DLY[4]")
pin ("BITS_NDE_DLY[5]")
pin ("BITS_NDE_DLY[6]")
pin ("BITS_NDE_DLY[7]")
foreach item $data {
if {[regexp {(\w+)\[(\d+)\]} $item -> keyword number]} {
if [dict exists $keyword_numbers $keyword] {
dict set keyword_numbers $keyword bit_start [expr {min([dict get $keyword_numbers $keyword bit_start], $number)}]
dict set keyword_numbers $keyword bit_end [expr {max([dict get $keyword_numbers $keyword bit_end], $number)}]
} else {
dict set keyword_numbers $keyword [dict create bit_start $number bit_end $number]
}
}
}
where the data in $keyword_numbers is
BITS_CLK2QDLY {bit_start 0 bit_end 1} BITS_DCC_MAIN {bit_start 0 bit_end 6} BITS_FIXDLY_MAIN {bit_start 0 bit_end 7} BITS_NDE_DLY {bit_start 0 bit_end 7}
in the below code, I am unable to create/generate the above-mentioned format
set bus_counter -1
proc add_bus_type {base_type data_type bit_width bit_from bit_to downto} {
foreach {bus_name {bit_start bit_end}} [dict sort $keyword_numbers] {
set bus [incr bus_counter]
set bus_type "bus$bus_counter"
set bit_width [expr $bit_end - $bit_start + 1]
set bit_to $bit_start
set bit_from $bit_end
add_bus_type $bus_type "array" $bit_width $bit_from $bit_to "true"
}
}
Please analyze the above code and let me know the update or any changes.
any doubts/ clarification Please add comments

Am I right that you don't want to call add_bus_type recursively - just generate the "structure" as "text". Then you could start with this:
proc add_bus_type {keyword_numbers} {
set bus_counter -1
foreach bus_name [lsort [dict keys $keyword_numbers]] {
set base_type "array"
set data_type "bit"
set bit_start [dict get $keyword_numbers $bus_name bit_start]
set bit_end [dict get $keyword_numbers $bus_name bit_end]
set bus [incr bus_counter]
set bus_type "bus$bus_counter"
set bit_width [expr $bit_end - $bit_start + 1]
set bit_to $bit_start
set bit_from $bit_end
set downto "true"
puts [format "type (%s) \{" $bus_type]
foreach i {base_type data_type bit_width bit_from bit_to downto} {
puts [format "\t$i : %s \;" [set $i]]
}
puts "\}"
puts [format "bus (%s) \{" $bus_name]
puts [format "\tbus_type : %s ;\n\}" $bus_type]
#puts [list $bus_type "array" $bit_width $bit_from $bit_to "true"]
}
}
calling add_bus_type $keyword_numbers
gives the following output
type (bus0) {
base_type : array ;
data_type : bit ;
bit_width : 2 ;
bit_from : 1 ;
bit_to : 0 ;
downto : true ;
}
bus (BITS_CLK2QDLY) {
bus_type : bus0 ;
}
. . .
type (bus3) {
base_type : array ;
data_type : bit ;
bit_width : 8 ;
bit_from : 7 ;
bit_to : 0 ;
downto : true ;
}
bus (BITS_NDE_DLY) {
bus_type : bus3 ;
}

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 script to print range of lines and create the variables

Hi I am having a code as stated below
module abcd( a , b , c ,da , fa, na , ta , ma , ra ,
ta, la , pa );
input a , b, da ,fa , na , ta , ma;
output c , ra ,ta ,
la ,pa ;
wire a , b , da , fa ,na ,
ta , ma;
// MBIST Structures
mbist_hsm_p::mbist_out_hsm_t mbist_out_hsm;
mbist_hsm_p::mbist_in_hsm_t mbist_in_hsm;
// HMS
kkkks ;
jsskks;
endmodule
Need to take the range between "MBIST Structures " and "//" and the take the first line as a input variable and second line as a output variable.
For example , I am trying below stated code
proc findrange {data start {stop ;}} {
# Find the starting pattern
set x1 [string first $start $data]
if {$x1 < 0} {
# Pattern not found
return
}
# Skip the pattern
incr x1 [string length $start]
# Find the ending pattern after the starting position
set x2 [string first $stop $data $x1]
if {$x2 < 0} {
# Return the remainder of the data when no ending pattern is found
return [string range $data $x1 end]
} else {
# Return the text between the starting and ending patterns
return [string range $data $x1 [expr {$x2 - 1}]]
}
}
set chan [open "mode.v"]
set code [read $chan]
close $chan
set var4 [ findrange $code "MBIST Structures" \/\/]
echo $var4 is printing these variables
mbist_hsm_p::mbist_out_hsm_t mbist_out_hsm;
mbist_hsm_p::mbist_in_hsm_t mbist_in_hsm;
I want to have two lists
$input should be "mbist_hsm_p::mbist_out_hsm_t mbist_out_hsm;"
$output should be "mbist_hsm_p::mbist_in_hsm_t mbist_in_hsm;"
How to create these variables from the var4 variable
When I am trying out to print out the $var4 variable , it is printing 4 independent variables
foreach p $var4 {
echo $p
}
mbist_hsm_p::mbist_out_hsm_t
mbist_out_hsm;
mbist_hsm_p::mbist_in_hsm_t
mbist_in_hsm;
Rather it should be " mbist_hsm_p::mbist_out_hsm_t mbist_out_hsm;"
and other one should be "mbist_hsm_p::mbist_in_hsm_t mbist_in_hsm;"
Two lists I am looking for
$input and $output
With a short input file like this, it is much easier to read the whole file into a variable. And for the described task I think string first is a better choice than string match.
So this is how I would do it:
proc findrange {data start {stop ;}} {
# Find the starting pattern
set x1 [string first $start $data]
if {$x1 < 0} {
# Pattern not found
return
}
# Skip the pattern
incr x1 [string length $start]
# Find the ending pattern after the starting position
set x2 [string first $stop $data $x1]
if {$x2 < 0} {
# Return the remainder of the data when no ending pattern is found
return [string range $data $x1 end]
} else {
# Return the text between the starting and ending patterns
return [string range $data $x1 [expr {$x2 - 1}]]
}
}
set chan [open "mod1.v"]
set code [read $chan]
close $chan
set out [open "output.file.txt" "w"]
puts $out [findrange $code input]
puts $out [findrange $code output]
close $out
There is some change in the white space between your input and the desired
output you specified. But you didn't indicate the rules for that transformation and they are not obvious. So, I am ignoring that for the moment.

How to extract the required lines from a file using TCL?

My file looks like this below. I want to extract only id, pos and type from file so that I can use it further. Should I need to treat this data as a list and use lindex syntax to retrieve.
{particles {id pos type v f}
{0 442.3601602032813 775.8494355067412 339.7428247245854 0 -1.0649468656691174 0.3118359585805807 0.7974629587243917 -7.856415738784473 120.82920096524781 80.7680149353967}
{1 75.78431491144367 187.28007812237516 279.3569202413006 0 0.3317344469183915 3.0716559473604916 -1.679965732986453 2.573367640795655 -11.46026754809828 125.75306472245369}
{2 44.167251258358164 371.8839725825084 80.32709197838003 0 -0.6260768510694417 0.9493261445672099 0.9445444874655268 -98.8132600015945 -80.10617403827258 43.578514821777155}
{3 289.0168944249348 193.4364952458922 96.30251497465443 0 -0.5327035586676473 1.028492567403681 4.364969924730662 139.09290151549465 75.46717320097427 -29.955066397359094}
{4 324.94257366360085 404.9215380451672 799.3481016151578 0 -1.2801842708841038 -4.320355658821216 2.9394195915181567 -109.72971491904342 -44.06068452005151 118.2802261191011}
{5 598.4521733790556 447.74320547029174 750.4399422142899 0 1.740414834859398 -0.5926143788565617 1.5937397085063156 -155.08309023301794 186.08101953841978 177.1804659692657}
}
This is the code I have used below. Can anyone tel me the code which I used is correct or not.
set num_part 6
set mol1 0.1666
set mol2 0.8333
set num_conf 2
for {set i 0} {$i < $num_conf} {incr i} {
set f [open "config_$i" "r"]
set part [while { [blockfile $f read auto] != "eof" } {} ]
set g [open "positions" "w"]
blockfile $g write particles {id pos type}
close $f
close $g
set g [open "positions" "r"]
set data [read $g]
close $g
set num0 0
for {set j 0} {$j < [expr { $num_part + 1 }]} {incr j} {
set type [lindex $data 0 $j 4]
if { $type == 0 } {
set tlist [expr $i]
set x0 [lindex $data 0 $j 1]
set y0 [lindex $data 0 $j 2]
set z0 [lindex $data 0 $j 3]
set total1 [ expr { sqrt(($x0 * $x0) + ($y0 * $y0)+ ($z0 * $z0)) }]+0]
incr num0
puts " $i :: $num0 "
set dum 0
for {set k 0} {$k < [expr { $num_part + 1 }]} {incr k} {
set type [lindex $data 0 $k 4]
if { $type == 1 } {
set tlist [expr $i]
set x1 [lindex $data 0 $k 1]
set y1 [lindex $data 0 $k 2]
set z1 [lindex $data 0 $k 3]
set total2 [ expr { sqrt(($x1 * $x1) + ($y1 * $y1)+ ($z1 * $z1)) }]+0]
incr dum
puts " $i :: $dum "
}
}
}
}
}
set h [open "dist12" "w"]
set dist12 [ expr {($mol1 * $total1)-($mol2 * $total2)}]
puts "Distance between cross particles $dist12"
puts $h "\# t $dist12 "
foreach t $tlist dist" $dist12 { puts $h "$t $dist_12" }
close $h
You've a few lines that look suspicious.
1:
set part [while { [blockfile $f read auto] != "eof" } {} ]
The result of while is an empty string, so the above code probably isn't doing what you hope. Not quite sure how to fix it though; blockfile isn't a standard Tcl command.
2:
for {set j 0} {$j < [expr { $num_part + 1 }]} {incr j} {
Not really a correctness issue, but that could be written as:
for {set j 0} {$j < $num_part + 1} {incr j} {
The bytecode generated will be virtually identical, but it's shorter and easier to read.
3:
set tlist [expr $i]
This looks unnecessary and suspcious. We know i is a numeric variable (in fact it contains an integer), so there's no need to pretend it is an expression. It slows things down for no benefit.
You've two occurrences of this.
4:
set total1 [ expr { sqrt(($x0 * $x0) + ($y0 * $y0)+ ($z0 * $z0)) }]+0]
This line is definitely wrong. The number of ] characters doesn't match the number of [ characters, so what you get will be “unexpected”, and that +0 is either useless or harmful. It's probably best to write a procedure to help you with this. Put the procedure at the top of the script.
proc length {x y z} {
expr { sqrt($x*$x + $y*$y + $z*$z) }
}
Then just call it later on:
set total1 [length $x0 $y0 $z0]
The same applies to the calculation of total2 later.
5:
foreach t $tlist dist" $dist12 { puts $h "$t $dist_12" }
Looks like this has a typo: dist" instead of dist. The failure to use dist inside the loop also looks odd; I think you're going wrong here, and should take another look and think about what you actually want to do.

how to check adjacent values in tcl list?

I have a list like
set val [ list Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1 ]
now i want to put it in a loop and execute some commands over each range
like
set number 0
set pattern 0
foreach n $val {
if {$pattern == 0} {
set current $n
regexp {(.*/)(\d+)} $n - pattern number
continue
}
regexp {(.*/)(\d+)} $n - match1 match2
if {$match1 == $pattern} {
#puts "someproc $current - match2"
}
}
I am unable to get this work the output should be like for ech pair or singular value found
someproc Fa2/0/1 - 24
someproc Gi1/0/13 - 23
someproc Gi1/1/1 - 1 #for singular values
someproc Gi2/0/1 - 1
EDIT : i have a list of such data like :
Gi3/0/1 Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1 Te1/0/1
where you can say each data can be of type Gi3/0/ or Gi2/0/ or Fa2/0/ these reperesent some range of ports on cisco swicth.Now for every type i need to execute some command for a range.Again taking the above list i can get.
somecommand Gi3/0/1 - 1 # there is only one `Gi3/0/` with number 1.
somecommand Fa2/0/1 - 24 # range of `Fa2/0/` is 1 to 24
similarly,
somecommand Gi1/0/13 - 23
somecommand Gi1/1/1 - 1
and so on
#!/usr/bin/tcl
## Assumptions:
## The pattern will always be X/X/X
## The values are given in list
set val_list [list Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1]
array set pattern {}
foreach item $val_list {
set parent [file dir $item]
set val [file tail $item]
if {[info exists pattern($parent,L)] && [info exists pattern($parent,H)] } {
if {$pattern($parent,L) > $val } {
set pattern($parent,L) $val
} elseif { $pattern($parent,H) < $val} {
set pattern($parent,H) $val
}
} else {
set pattern($parent,L) $val
set pattern($parent,H) $val
}
}
array set count {}
foreach pat [array names pattern] {
set pat [lindex [split $pat ,] 0]
if {![info exists count($pat)] } {
puts "$pat $pattern($pat,L) - $pattern($pat,H)"
set count($pat) 1
}
}
/*The output will be
Gi1/0 13 - 23
Fa2/0 1 - 24
Gi2/0 1 - 1
Gi1/1 1 - 1
*/
Hope this is what you are requesting for. I used array "count" to remove duplicate entries in output, which needs to be avoided. Hope if someone can suggest any better way. And FYI I am using 8.4 version of TCL.
If you are not sure how arrays, work, you can edit the code you posted as an answer to this code:
set number 0
set pattern 0
set current 0
set result [list Gi3/0/1 Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1 Te1/0/1]
foreach n [lsort $result] {
if {$pattern == 0} {
set current $n
regexp {(.*/)(\d+)} $n - pattern number
continue
}
regexp {(.*/)(\d+)} $n - match1 match2
if {$match1 == $pattern} {
set number $match2
} else {
puts "$current - $number"
set pattern $match1
set number $match2
set current $n
}
}
That works for me :)
The output (note that I sorted the list first so you only have to worry about the increasing $number or $match2 while not having to bother too much about the $pattern):
Fa2/0/1 - 24
Gi1/0/13 - 23
Gi1/1/1 - 1
Gi2/0/1 - 1
Gi3/0/1 - 1
Here is my solution, which does not use array (nothing is wrong with array, my solution just don't need it), and it does it in one pass (i.e. only one loop).
set val [ list Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1 ]
set lastPattern ""
set lastNumber 0
lappend val x/1/1; # Add a trailer to ease processing
foreach item $val {
# If item=Fa2/0/1, then pattern=Fa2/0 and number=1
set pattern [file dirname $item]
set number [file tail $item]
if {$pattern == $lastPattern} {
# We have seen this pattern before
puts "$pattern/$lastNumber - $number"
set lastPattern ""
} else {
# This is a new pattern, print the old one if applicable then
# save the pattern and number for later processing
if {$lastPattern != ""} {
puts "$lastPattern/$lastNumber - $lastNumber"
}
set lastPattern $pattern
set lastNumber $number
}
}
set val [lrange $val end-1]; # Remove the trailer
If you want to compare adjacent list elements, it might be cleaner to use a C-style for loop:
for {set i 0} {$i < [llength $val] - 1} {incr i} {
set current [lindex $val $i]
set next [lindex $val [expr {$i+1}]]
# ...
}
Or, a bit more esoteric
set l {a b c d e f g}
foreach current [lrange $l 0 end-1] \
next [lrange $l 1 end] {
puts "$current $next"
}
outputs
a b
b c
c d
d e
e f
f g
You could even write a new control structure, similar to Ruby's each_cons
proc foreach_cons {vars list body} {
foreach varname $vars {upvar 1 $varname $varname}
set numvars [llength $vars]
for {set i 0} {$i <= [llength $list]-$numvars} {incr i} {
lassign [lrange $list $i [expr {$i + $numvars}]] {*}$vars
uplevel 1 $body
}
}
foreach_cons {a b c} $l {puts "$a $b $c"}
a b c
b c d
c d e
d e f
e f g
Why don't you loop over pairs of the list?
foreach {v1 v2} $val {
someproc $v1 $v2
}
You might check if both values are similar, extract the parts that you need etc.
I came up with a awkward solution of my own :
where reslut is the list :
Gi3/0/1 Fa2/0/1 Fa2/0/24 Gi1/0/13 Gi1/0/23 Gi1/1/1 Gi2/0/1 Te1/0/1
#
set number 0
set pattern 0
set last_element [lindex $result end]
set first_element [lindex $result 0]
foreach n $result {
if {$pattern == 0} {
set current $n
set count 0
regexp {(.*/)(\d+)} $n - pattern number
continue
}
regexp {(.*/)(\d+)} $n - match1 match2
if {$match1 == $pattern} {
set count 0
puts " $current - $match2"
continue
} else {
if {"$last_element" == "$n"} {
puts "$last_element"
}
if {"$first_element" == "$current"} {
puts "$first_element"
}
incr count
if {"$count" == 1} {
set pattern $match1
set current $n
continue
} else {
if {$match1 != $pattern} {
puts "$current"
}
}
set pattern $match1
}
set current $n
}
This solution is a little shorter, but requires Tcl 8.5.
First, create a dictionary structure with the first two fields as key and subkey, and collect lists of values from the third field as dictionary values:
set data {}
foreach v $val {
lassign [split $v /] a b c
if {![dict exists $data $a $b]} {
dict set data $a $b {}
}
dict with data $a {
lappend $b $c
set b [lsort –integer $b]
}
}
Then iterate over this dictionary structure, calling the someproc command for each combination of key, subkey, first and last value.
dict for {a v} $data {
dict for {b v} $v {
someproc $a/$b/[lindex $v 0] - [lindex $v end]
}
}
Documentation: dict, foreach, if, lappend, lassign, lindex, set, split

example code to make puts log the output

I'm looking for some Tcl code that would duplicate what puts command sends to stdout to some log file. Yes, there is a possibility to change all calls to puts to some custom function. But I would like to make it as transparent as possible.
I have this trial code, but it doesn't really work that well:
set pass_log_output "0"
rename puts _puts
proc puts { args } {
global pass_log_output
if {[info exists pass_log_output]} {
# There can be several cases:
# -nonewline parameter, stdout specified or not
set stdout_dest [ lsearch $args stdout ]
set nonewline [ lsearch $args -nonewline ]
if { $stdout_dest != -1 } {
log_low_level "" [lindex $args [expr $stdout_dest + 1]] ""
} elseif { $nonewline != -1 && [ llength $args ] > 1} {
log_low_level "" [lindex $args [expr $nonewline + 1]] ""
} else {
log_low_level "" [lindex $args 0] ""
}
}
if { [ catch { eval _puts $args } err ] } {
return -code error $err
}
}
log_low_level function just stores the passed string in a file.
So far I'm getting this error:
Tcl Interpreter Error: too many nested evaluations (infinite loop?)
Does log_low_level use puts? That could be your infinite loop.
If so, try changing it to use _puts.
Thanks for the points. I just want to post the final working code for reference. It even takes care of the storing lines with -nonewline flag properly.
set pass_log_output "0"
set last_call_nonewline 0
rename puts _orig_puts
proc puts { args } {
global pass_log_output
global g_log_file
global last_call_nonewline
if {[info exists pass_log_output]} {
# Check if the logging was initialized
if {![info exists g_log_file]} {
_orig_puts "Log file wasn't initialized!"
return
}
# There can be several cases:
# -nonewline parameter, stdout specified or not
set stdout_dest [ lsearch $args stdout ]
set nonewline [ lsearch $args -nonewline ]
if {[ llength $args ] > 3} {
return -code error "wrong # args: should be puts ?-nonewline? ?channelId? string"
} elseif { $stdout_dest != -1 } {
set message [lindex $args end]
} elseif { $nonewline != -1 && [ llength $args ] == 2} {
set message [lindex $args [expr $nonewline + 1]]
} elseif {[ llength $args ] == 1} {
set message [lindex $args 0]
}
# Store the message in the file, if needed.
# Take into account if the last call was with -nonewline
if {[info exists message]} {
if {$last_call_nonewline == 0} {
_orig_puts -nonewline $g_log_file [clock format [clock seconds] -format "%T - "]
}
if {$nonewline != -1} {
set last_call_nonewline 1
_orig_puts -nonewline $g_log_file "$message"
} else {
set last_call_nonewline 0
_orig_puts $g_log_file "$message"
}
flush $g_log_file
}
}
if { [ catch { eval _orig_puts $args } err ] } {
return -code error $err
}
}
Since puts has very few options, it may be easier to consider the number of args given. Also, you should contain all uses of the original _puts to your new puts proc -- this new puts should be transparent even to your code.
I assume you only want to log stuff you're writing to stdout
rename puts _orig_puts
proc puts {args} {
switch -exact [llength $args] {
3 {
# both -newline and a channelId are given
set do_log [expr {[lindex $args 1] eq "stdout"}]
}
2 {
# only log if not writing to stdout
set chan [lindex $args 0]
set do_log [expr {$chan eq "-nonewline" || $chan eq "stdout"}]
}
1 {
set do_log true
}
default {
error {wrong # args: should be "puts ?-nonewline? ?channelId? string"}
}
}
if {$do_log} {
set chan [open $::mylogfile a]
_orig_puts $chan [lindex $args end]
close $chan
}
_orig_puts {*}$args
}