Passing a list to a proc tcl - list

I already looked at Passing list to Tcl procedure, and I couldn't quite understand how to do it properly.
To put it in context, this is how I'm passing the list:
switch $choice {
n {
set ptable [new_partition {$ptable}]
}
d {
set ptable [delete_partition {$ptable}]
}
p {
set ptable [print_table {$ptable}]
}
w {
set ptable [write_table {$ptable $fn}]
puts "Saving file...\nExiting..."
break
}
q {
puts "Exiting..."
break
}
default {
puts "Illegal option"
}
}
This is an example of one of the procedures
proc print_table {ptable} {
# Set format string
set formatStr {%-16s%-8s%-8s%-8s%-8s%-8s}
# Print the contents of ptable to stdout
puts [format $formatStr "\nPartition" "Start" "End" "Blocks" "ID" "System"]
puts "--------------------------------------------------------"
foreach item $ptable {
set parts [lindex $item 0]
set sCyl [lindex $item 1]
set eCyl [lindex $item 2]
set blok [lindex $item 3]
set id [lindex $item 4]
set sys [lindex $item 5]
puts [format $formatStr $parts $sCyl $eCyl $blok $id $sys]
}
return $ptable
}
Ptable is being created correctly, but it loses all its information as soon as I pass it to one of the procedures. I've tried passing it with "{*}$ptable" but it returns an error. Everything else in my program is working perfectly (if I take the code from any single procedure and put it by itself, it all works), I just can't seem to get it to pass the list properly.

Don't use braces here: new_partition {$ptable} -- braces inhibit variable expansion, and you are passing the 7-character string
$ptable
See rule #6 in http://tcl.tk/man/tcl8.6/TclCmd/Tcl.htm
Just pass the variable: new_partition $ptable
Similarly:
delete_partition $ptable
print_partition $ptable
write_partition $ptable $fn
The print_table procedure you've shown does not actually modify the argument you pass to it, so you don't really need to return a value.
Also, you don't really need to decompose the rows of ptable into individual variables if you're just passing them to format. You can turn that proc into
# Print the contents of ptable to stdout
proc print_table {ptable} {
set formatStr {%-16s%-8s%-8s%-8s%-8s%-8s}
puts [format $formatStr "\nPartition" Start End Blocks ID System]
puts "--------------------------------------------------------"
foreach item $ptable {
puts [format $formatStr {*}$item]
}
}
And don't do this
set ptable [print_table $ptable]
but do this
print_table $ptable

Related

conceptual error with control flow in Tcl

I have a collection of things, some of them empty.
I want to form a collection of non empty things, separated by separator.
this is essentially what I do in c++, but fails with any and all combinations of $ signs etc. etc.
I've already got a work around thanks, I'd like to know why and how this fails.
set q1 "a"
set q2 ""
set q3 "c"
set q4 d
set q5 ""
set answer ""
set needSeparator 0
foreach { var } {
q1 q2 q3 q4 q5
} {
if { $var ne "" } {
if {$needSeparator} {
append answer " separator "
}
append answer $var
set needSeparator 1
}
}
# expecting answer to be "a separator c separator d"
puts $answer
edit 2021-09-14
Following on from #Shawn
< if { $var ne "" } {
---
> if { [set elem [set $var]] ne "" } {
< append answer $var
---
> append answer $elem
on my effort does the job.
not quite sure how set is doing the dereferencing there
but that's one for another day.
this was a minimal example so the rather more funky answers are too involved for someone trying to program in c++ :-). The qNs are
horrible and come from different places, but the final code example is
sweet and works translated back into my real problem - see below
# build compound SELECT
set q1 [select $mapText "final_text"]
set q2 [select $parish "parish"]
set q3 [select $la "local_authority"]
set q4 [sqSelect $five00]
set q5 ""
if {$nation ne "All"} {
set q5 {SELECT pin_id AS id FROM gazetteer WHERE nation = '}
append q5 $nation "'\n"
}
set compound {}
foreach clause {q1 q2 q3 q4 q5} {
if {[set q [set $clause]] ne ""} {
lappend compound $q
}
}
if {[llength compound] == 0} { return ""}
set res "WITH pinIds AS (\n"
append res [join $compound "INTERSECT\n "] ")\n"
Thanks for your help
You're better off using a list, dict or array to store related values instead of a bunch of different variables. But any way your data is stored, lappend the non-empty values to a list or otherwise filter out the empty ones and join the result:
#!/usr/bin/env tclsh
set data {a "" c d ""}
# Using foreach
set answer {}
foreach elem $data {
if {$elem ne ""} {
lappend answer $elem
}
}
puts [join $answer " separator "]
# Using lmap for a more functional style; note eq instead of of ne
set answer [lmap elem $data { if {$elem eq ""} continue; set elem }]
puts [join $answer " separator "]
# Using a dict
set data [dict create q1 a q2 "" q3 c q4 d q5 ""]
set answer {}
# Dict traversal happens in the same order keys were added
dict for {_ elem} $data {
if {$elem ne ""} {
lappend answer $elem
}
}
puts [join $answer " separator "]
When iterating through a list of variable names, you have to use set to get the value of the current name (In your code, $var is q1, q2, etc. which are always going to be not equal to an empty string):
set answer {}
foreach varname {q1 q2 q3 q4 q5} {
set elem [set $varname]
if {$elem ne ""} {
lappend answer $elem
}
}
puts [join $answer " separator "]
Not an answer, but a response to the array for comments on Shawn's answer
A Tcl implementation of array for
proc array_for {vars arrayName body} {
if {[llength $vars] != 2} {
error {array for: "vars" must be a 2 element list}
}
lassign $vars keyVar valueVar
# Using the complicated `upvar 1 $arrayName $arrayName` so that any
# error messages propagate up with the user's array name
upvar 1 $arrayName $arrayName \
$keyVar key \
$valueVar value
set sid [array startsearch $arrayName]
# If the array is modified while a search is ongoing, the searchID will
# be invalidated: wrap the commands that use $sid in a try block.
try {
while {[array anymore $arrayName $sid]} {
set key [array nextelement $arrayName $sid]
set value [set "${arrayName}($key)"]
uplevel 1 $body
}
} trap {TCL LOOKUP ARRAYSEARCH} {"" e} {
puts stderr [list $e]
dict set e -errorinfo "detected attempt to add/delete array keys while iterating"
return -options $e
} finally {
array donesearch $arrayName $sid
}
return
}
and add to the array ensemble:
set map [namespace ensemble configure array -map]
dict set map for ::array_for
namespace ensemble configure array -map $map
Given that, an array values subcommand can be easily created (to pair with array names)
proc array_values {arrayName} {
upvar 1 $arrayName ary
set values [list]
array for {name value} ary {lappend values $value}
return $values
}

Parsing the data from a verilog file

I am a beginner in TCL scripting. I am working on parsing the data from a Verilog file to a xls file as below.
Input Verilog file contains following data:
Inferred components
Operator Signedness Inputs Outputs CellArea Line Col Filename
=====================================================================================================
apn
u_apn_ttp_logic
abc
u_apn_wgt_op_rd_u_apn_sca_u_part_sel1_sl_69_33
module:shift_right_vlog_unsigned_4662_7709
very_fast/barrel >> x 25x5 25 223.02 69 33 part_select.v
=====================================================================================================
apn
u_apn_ttp_logic
u_apn_wgt_op_rd_u_apn_scale_u_part_sel1_sub00283545
module:sub_signed_4513_5538
very_fast - signed 11x24 25 152.80 0 0 a
=====================================================================================================
(This is a long file…)
The parsing will end after the last section:
=====================================================================================================
apn
u_apn_start_ctrl_final_adder_add_212_41
module:add_unsigned_carry_882
very_fast + unsigned 32x32x1 32 120.39 212 41 feu_start_ctrl.v
=====================================================================================================
I want to extract the data as below , consider first section
Top name=apn
Instance=u_apn_ttp_logic/abc/u_apn_wgt_op_rd_u_feu_scale_u_part_select1_srl_69_33
Module = shift_right_vlog_unsigned_4662_7709
Architecture=very_fast/barrel
Operator = >>
Sign=x
Input Size = 25x5
Output = 25
Area = 223.02
Column = 69
Row = 33
File Name = part_select.v
I am stucked at a point while implementing this.
below is my approach for the same:
set fd "[open "path_data.v" r]"
set flag 0
while {[gets $fd line] !=-1} {
if {[regexp "\===*" $line]} {
while {[gets $fd line] >= 0} {
append data "$line "
if {[regexp "\====*" $line]} {
break
} }
set topname [lindex $data 0]
regexp {(^[a-z]*) (.*) (.*module)} $data match topname instance sub3
puts "top name :$topname "
puts "instance: $instance"
}
close $fd
I am able to output topname and instance name only, not other values
Also please help me extract these values.
With this sort of task, it really helps if you put parts of the task into procedures that do just a simpler bit. For example, suppose we were to split the processing of a single section into its own procedure. Since it is only going to do one section (presumably a lot shorter than the overall file), it can work on a string (or list of strings) instead of having to process by lines, which will make things greatly easier to comprehend.
For example, it would handle just this input text:
apn
u_apn_ttp_logic
abc
u_apn_wgt_op_rd_u_apn_sca_u_part_sel1_sl_69_33
module:shift_right_vlog_unsigned_4662_7709
very_fast/barrel >> x 25x5 25 223.02 69 33 part_select.v
We might handle that like this:
proc processSection {sectionText} {
set top ""
set instance ""
set module ""
set other {}
foreach line [split $sectionText "\n"] {
if {$top eq ""} {
set top [string trim $line]
continue
}
if {$module eq ""} {
# This regular expression matches lines starting with “module:” and
# extracts the rest of the line
if {[regexp {^module:(.*)} $line -> tail]} {
set module [string trim $tail]
} else {
append instance [string trim $line] "/"
}
continue
}
# This regular expression matches a sequence of non-space characters, and
# the -all -inline options make regexp return a list of all such matches.
lappend other {*}[regexp -all -inline {\S+} $line]
}
# Remember to remove trailing “/” character of the instance
set instance [string trimright $instance /]
# Note that this splits apart the list in $other
outputSectionInfo $top $instance $module {*}$other
}
We also need something to produce the output. I've split it into its own procedure as it is often nice to keep parsing separate from output.
proc outputSectionInfo {top instance module arch op sgn in out area col row file} {
# Output the variables
foreach {label varname} {
"Top name" top
"Instance" instance
"Module" module
"Architecture" arch
"Operator" op
"Sign" sgn
"Input Size" in
"Output" out
"Area" area
"Column" col
"Row" row
"File Name" file
} {
# Normally, best to avoid using variables whose names are in variables, but
# for printing out like this, this is actually really convenient.
puts "$label = [set $varname]"
}
}
Now that we've got a section handler and output generator (and you can verify for yourself that these do sensible things, as they're quite a bit simpler than what you were trying to do in one go), we just need to feed the sections from the file into it, skipping over the header. The code does that, and just does that.
set fd [open "path_data.v"]
set flag 0
while {[gets $fd line] >= 0} {
if {[regexp {^=====+$} $line]} {
if {$flag} {
processSection [string trimright $accumulator "\n"]
}
set flag 1
set accumulator ""
} else {
append accumulator $line "\n"
}
}
close $fd
Your immediate problem was that your code was closing the channel too early, but that was in turn caused by your confusion over indentation, and that was in turn caused by you trying to do too much in one place. Splitting things up to make the code more comprehensible is the fix for this sort of issue, as it makes it much easier to tell that the code is definitely correct (or definitely wrong).
I worked on above script, and here is my code for the same. This code won't work if there is an empty line after "========" line
But I would like to explore your code as it is well organised.
set fd "[open "path_data.v" r]"
set fd1 "[open ./data_path_rpt.xls w+]"
puts $fd1 "Top Name\tInstance\tModule\tArchitecture\tOperator\tSign\tInput Size\tOutput size\tArea\tLine number\tColumn number\tFile Name"
set data {}
while {[gets $fd line] !=-1} {
if {[regexp "\===*" $line]} {
set data {}; set flag 0
while {[gets $fd line] >= 0} { append data "$line "
if {[regexp {[a-z]\.v} $line]} { set flag 1;break} }
puts "$data\n\n"
if {$flag} {
set topname [lindex $data 0]
regexp {(^[a-z]*) (.*) (.*module\:)(.*)} $data match topname instance sub3 sub4
set inst_name {} ;
foreach txt $instance {
append inst_name "$txt\/"
}
set instance [string trim $inst_name "\/"]
set module [lindex $sub4 0]
set architecture [lindex $sub4 1]
set operator [lindex $sub4 2]
set sign [lindex $sub4 3]
set input_size [lindex $sub4 4]
set output_size [lindex $sub4 5]
set area [lindex $sub4 6]
set linenum [lindex $sub4 7]
set col_num [lindex $sub4 8]
set file_name [lindex $sub4 9]
puts $fd1 "$topname\t$instance\t$module\t$architecture\t$operator\t$sign\t$input_size\t$output_size\t$area\t$linenum\t$col_num\t$file_name"
set data {} ; set flag 0
}}
}
close $fd1
close $fd

regexp loop to find first instance of each query TCL

I have a list variable containing some values:
lappend list {query1}
{query2}
{query3}
And some data in file1 with parts of them matching the values above
query1 first data
query1 different data
query1 different data
query2 another data
query2 random data
query3 data something
query3 last data
How do I create a regexp loop that catches only the first instance found of each query and prints them out? In this case the output would be:
query1 first data
query2 another data
query3 data something
Attempted code to produce the output
set readFile1 [open file1.txt r]
while { [gets $readFile1 data] > -1 } {
for { set n 0 } { $n < [llength $list] } { incr n } {
if { [regexp "[lindex $list $n]" $data] } {
puts $data
}
}
}
close $readFile1
I tried using a for loop while reading the data from a file, but it seems to catch all values even if the -all option is not used.
You can either read the file as a whole into a variable using read command, if the text file is smaller in size. Apply the regexp for the content and we can extract the required data.
set list {query1 query2 query3}
set fp [open file1.txt r]
set data [read $fp]
close $fp
foreach elem $list {
# '-line' flag will enable the line sensitive matching
if {[regexp -line "$elem.+" $data line]} {
puts $line
}
}
If suppose the file too large to hold or if you consider run-time memory usage, then go ahead with the reading the content line by line. There we need to have control on what already matched for which you can keep an array to maintain whether the first occurrence of any query matched or not.
set list {query1 query2 query3}
set fp [open file1.txt r]
array set first_occurence {}
while {[gets $fp line]!=-1} {
foreach elem $list {
if {[info exists first_occurence($elem)]} {
continue
}
if {[regexp $elem $line]} {
set first_occurence($elem) 1
puts $line
}
}
}
close $fp
Reference : regexp
package require fileutil
set queries {query1 query2 query3}
set result {}
::fileutil::foreachLine line file1.txt {
foreach query $queries {
if {![dict exists $result $query]} {
if {[regexp $query $line]} {
dict set result $query $line
puts $line
}
}
}
}
The trick here is to store the findings in a dictionary. If there is a value corresponding to the query in the dictionary already, we don’t search for it again. This also has the advantage that the found lines are available to the script after the search and aren’t just printed out. The regexp search looks for the query string anywhere in the line: if it should only be in the beginning of the line, use regexp ^$query $line instead.
Documentation: dict, fileutil package, foreach, if, package, puts, regexp, set
Try This,
set fd [open "query_file.txt" r]
set data [read $fd]
set uniq_list ""
foreach l [split $data "\n"] {
lappend uniq_list [lindex $l 0]
}
set uniq_list [lsort -unique $uniq_list]
foreach l $uniq_list {
if {[string equal $l ""]} {
continue
}
foreach line [split $data "\n"] {
if {[regexp $l $line]} {
puts "$line"
break
}
}
}
close $fd
References: file , list , regexp
Not using regexp at all: I assume your "query"s do not contain whitespace
set list [list query1 query2 query3]
array set seen {}
set fh [open file1]
while {[gets $fh line] != -1} {
set query [lindex [split $line] 0]
if {$query in $list && $query ni [array names seen]} {
set seen($query) 1
puts $line
}
}
query1 first data
query2 another data
query3 data something

Save procedure to file

I am starting to learn TCL languages so the question might be a little simple. I am looking to construct a matrix from vector. I found the following idea looking into previously asked question :
set phi_x [lrepeat 36 [lrepeat 12 0.]]
To create my list of vector. Then I populate the vector of the list with the command lset. I then use the following, which I found on another question threads :
proc printMatrix {myMatrix} {
set height [llength [lindex $myMatrix]]
set width [llength [lindex $myMatrix 0]]
for {set j 0} {$j < $width} {incr j} {
puts -nonewline \Phi$j
}
puts ""
for {set i 0} {$i < $height} {incr i} {
puts -nonewline $i
for {set j 0} {$j < $width} {incr j} {
puts -nonewline \t[lindex $myMatrix $i $j]
}
puts ""
}
This code works fine. Problem is I cannot seems to save the result of the procedure into a file using the
set varName [open file.out a]
puts $varName [printMatrix $myMatrix]
close $varName
Thanks for the help!
If you want to return a value from the procedure and print it, like this
puts $varName [printMatrix $myMatrix]
then you need to replace the puts -nonewline ... invocations with append res ..., and the puts "" invocations by append res \n, and finally, when the procedure is done, call return $res.
If you want the procedure to output text to a file, call it like this
printMatrix $varName $myMatrix
and redefine it like this
proc printMatrix {chan myMatrix} {
replacing puts -nonewline ... with puts -nonewline $chan ... and puts "" with puts $chan "".
Documentation: append, proc, puts, return

In Tcl, how to read in file by line and find a list of string (from another file) then append the line with a ##

I need to be able tp read in file by line and find a set of strings (from another file) that starts with the strings plus set of characters like ({ somedata }) then append the line with a ## to that block.
Here is what I have so far:
set mydir <path to my dir>
#file name file.txt with content:
~>cat file.txt
Strng00 {
some data
}
Strng021 {
some data
}
Strng02 {
some data
}
Strng03 {
some data
}
Strng_dt {
some data
}
Strng01 {
some data
}
Strng02 {
some data
}
Strng03 {
some data
}
Strng_dt {
some data
}
Strng42 {
some data
}
Strng412
--
set list { Strng01 Strng02 Strng03 Strng_dt Strng42 } # May be read in the list from another file which needs to be matched
set fileIn [lindex $argv 0]
set fileInId [open $mydir/file.txt r]
set appendLine 0
foreach item $list {
set j 0
while {[gets $fileInId line ] != -1} {
if [regexp -all -line $item $line] { set appendLine 1 }
if $appendLine {
if [regexp {^\s*\}\s*$} $line] { set appendLine 0 }
set line "## $line"
}
puts $line
}
set j 1
}
The result only shows the first entry of the list:
Strng00
Strng021
Strng02
Strng03
Strng_dt
##Strng01 {
## some data
##}
Strng02
Strng03
Strng_dt
Strng42
Strng412
I'd like to get ## after each of the items listed..
Thanks in advance.`
Here's another take:
while {[gets $fh line] != -1} {
set first [lindex [split [string trimleft $line]] 0]
puts [format "%s%s" [expr {$first in $list ? "##" : ""}] $line]
}
That finds the first word in the line, and checks if it is an element in $list.
Does this solve your problem?
set list { Strng01 Strng02 Strng03 Strng_dt Strng42 }
set fileInId [open $mydir/file.txt r]
while {[gets $fileInId line ] != -1} {
if {[regexp -line [join $list |] $line]} {
set line "## $line"
}
puts $line
}
edit: dealing with the updated specification.
This is one way to do it; it takes advantage of the fact that the lines in the file match Tcl command invocation syntax.
proc unknown {cmd args} {
set list { Strng01 Strng02 Strng03 Strng_dt Strng42 }
if {$cmd in $list} {
foreach line [split [info level 0] \n] {
puts "## $line"
}
} else {
puts [info level 0]
}
}
source file.txt
It works like this: using source on the file means that the Tcl interpreter tries to use the keywords on each line as command names, with the { ... } parts as arguments. Since the keywords aren't existing commands, the interpreter hands the invocations over to the unknown command, which we have redefined to recognize the relevant keywords and print the complete invocation ([info level 0]) with a ## prefix if they are in the list, or else just print the invocation as it is.
Documentation: chan, foreach, if, info, join, open, proc, puts, regexp, set, source, split, unknown, while