Treat argument as string in Tcl 'expr' statement - if-statement

I'm using the short syntax of 'if' statement like this:
proc WriteParameter {Parameter Value} {
# Ugly option - WORKS
if {$Parameter eq "Unique"} {
set Register ControlStatus
set Data ${Value}0
set Mask EF
} else {
set Register $Parameter
set Data $Value
set Mask {}
}
# Elegant option - DOESN'T WORK
set Register [expr {$Parameter eq "Unique" ? "ControlStatus" : $Parameter}]
set Data [expr {$Parameter eq "Unique" ? ${Value}0 : $Value}]
set Mask [expr {$Parameter eq "Unique" ? EF : {}}]
puts $Register-$Data-$Mask
return 0
}
set Value 4E20 ;# Merely hexadecimal number
set Parameter Regular
WriteParameter $Parameter $Value
The problem is that in the elegant option, since the 'expr' statement always treats its arguments as integers, 'Data' gets the value of 4e+20, which is merely the scientific notation of 'Value'.
However, I need 'Data' to be 'Value' (for example, to write to an external register).
Any ideas?

The expr language is rather different to the rest of Tcl. You have to care much more about syntax there. Putting things in "double quotes" can help.
set Register [expr {$Parameter eq "Unique" ? "ControlStatus" : $Parameter}]
set Data [expr {$Parameter eq "Unique" ? "${Value}0" : $Value}]
set Mask [expr {$Parameter eq "Unique" ? "EF" : {}}]
However, you might instead be better using if as that doesn't try to convert the results of its arms to a number (using approximately the rules for C constants) unlike expr.
set Register [if {$Parameter eq "Unique"} {string cat ControlStatus} {string cat $Parameter}]
set Data [if {$Parameter eq "Unique"} {string cat $Value 0} {string cat $Value}]
set Mask [if {$Parameter eq "Unique"} {string cat EF}]
# We can omit the else clause; the default is an empty string anyway
This depends on string cat, which was introduced in Tcl 8.6.3 (well, 8.6.2 but that had some bugs in it's I/O subsystem that you really want to avoid). If you're using anything from 8.5 up to 8.6.1, use this instead:
set Register [if {$Parameter eq "Unique"} {return -level 0 ControlStatus} {return -level 0 $Parameter}]
set Data [if {$Parameter eq "Unique"} {return -level 0 ${Value}0} {return -level 0 $Value}]
set Mask [if {$Parameter eq "Unique"} {return -level 0 EF}]
Yes, return -level 0 just gives the value as its result. “Obvious and discoverable”…
If you're on 8.4 or before still (Upgrade, man! You're out of security support!) then you need a little helper procedure:
proc value {value} {return $value}
set Register [if {$Parameter eq "Unique"} {value ControlStatus} {value $Parameter}]
set Data [if {$Parameter eq "Unique"} {value $Value 0} {value $Value}]
set Mask [if {$Parameter eq "Unique"} {value EF}]
The value procedure above will work with later versions of Tcl too, but it gives less efficient bytecode.
However, for all the above I'd actually do something different:
set Register $Parameter
set Data $Value
set Mask ""
if {$Parameter eq "Unique"} {
set Register ControlStatus
append Data 0
set Mask EF
}
I might also use scan to parse the value and format to do the composing of the results back to hex (if necessary). Like that, it'd be much easier to think about the value and not just the representation.

Related

Matching pattern and adding values to a list in Tcl

I wondered if I could get some advice, I'm trying to create a list by reading a file with input shown below
Example from input file
Parameter.Coil = 1
Parameter.Coil.Info = Coil
BaseUTC.TimeSet_v0 = 1
BaseUTC.TimeSet_v0.Info = TimeSet_v0
BaseUTC.TimeSet_v1 = 1
BaseUTC.TimeSet_v1.Info = TimeSet_v1
BaseUTC.TimeSet_v14 = 1
BaseUTC.TimeSet_v14.Info = TimeSet_v14
BaseUTC.TimeSet_v32 = 1
BaseUTC.TimeSet_v32.Info = TimeSet_v32
VC.MILES = 1
VC.MILES.Info = MILES_version1
I am interested in any line with prefix of "BaseUTC." and ".Info" and would like to save value after "=" in a list
Desired:
output = TimeSet_v0 TimeSet_v1 TimeSet_v14 TimeSet_v32
I've tried the following but not getting the desired output.
set input [open "[pwd]/Data/Input" r]
set list ""
while { [gets $input line] >= 0 } {
incr number
set sline [split $line "\n"]
if {[regexp -- {BaseUTC.} $sline]} {
#puts "lines = $sline"
if {[regexp -- {.Info} $sline]} {
set output [lindex [split $sline "="] 1]
lappend list $output
}}}
puts "output = $list"
close $input
I get output as
output = \ TimeSet_v0\} \ TimeSet_v1\} \ TimeSet_v14\} \ TimeSet_v32\}
Can any help identify my mistake, please.
Thank you in advance.
A lot of your code doesn't seem to match your description (Steering? I thought you were looking for BaseUTC lines?), or just makes no sense (Why split what you already know is a single line on newline characters?), but one thing that will really help simplify things is a regular expression capture group. Something like:
#!/usr/bin/env tclsh
proc process {filename} {
set in [open $filename]
set list {}
while {[gets $in line] >= 0} {
if {[regexp {^BaseUTC\..*\.Info\s+=\s+(.*)} $line -> arg]} {
lappend list $arg
}
}
close $in
return $list
}
puts [process Data/Input]
Or using wildcards instead of regular expressions:
proc process {filename} {
set in [open $filename]
set list {}
while {[gets $in line] >= 0} {
lassign [split $line =] var arg
if {[string match {BaseUTC.*.Info} [string trim $var]]} {
lappend list [string trim $arg]
}
}
close $in
return $list
}

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
}

How to extract only two fields using Regular Expression

In TCL I am writing the regular expression for below output:
Output args is
packet-filter 0
identifier 0
direction bidirectional
network-ip 10.7.98.231/32
ue-port-start 0
ue-port-end 0
nw-port-start 0
nw-port-end 0
protocol 1
precedence 0
packet-filter 1
identifier 1
direction uplink
network-ip 10.7.98.231/32
ue-port-start 0
ue-port-end 0
nw-port-start 0
nw-port-end 0
protocol 1
precedence 0
Output of my Regular Expression : regexp -all -inline {direction\s+(\S+)} $args is
{direction bidirectional} bidirectional {direction uplink} uplink
I need to extract the direction value which is bidirectional and uplink
Any suggestion ?
For the current case, where the captured substrings are chunks of non-whitespace text, you may re-build the output checking if each item has length set to 1:
set results [regexp -all -inline {direction\s+(\S+)} $args]
set res {}
foreach item $results {
if {[llength $item] == 1} {
lappend res $item
}
}
Then, $res will only hold bidirectional and uplink.
See the Tcl demo.
For a more generic case, you may use
set res {}
foreach {whole capture1} $results {
lappend res $capture1
}
See this Tcl demo
You may add more captureX arguments to accommodate all the capturing group values returned by your regex.
You simply need a loop or something equivalent. If you need to work on each direction individually, a foreach loop is appropriate:
set results [regexp -all -inline {direction\s+(\S+)} $args]
foreach {main sub} $results {
puts $sub
}
# bidirectional
# uplink
Or if you need the list of directions, then lmap sounds appropriate:
set directions [lmap {main sub} $results {set sub}]
# bidirectional uplink
The regexp is not absolutely necessary, you may process the value of args into a dictionary:
set d [dict create]
foreach {k v} $args {
dict lappend d $k $v
}
puts [dict get $d direction]

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

Parsing Values in columns using TCL

I have an output which looks like below
A B C
0 1 2
I have lot of coloumns due to which the o/p looks to have wrapped around. I am wondering a way to get the respective value if I parse the keys (A B or C)
Considering each key (i.e. alphabet) will have one value (i.e. numeral), then we can use the following way. (This might be a workaround to get what we need)
set input "A B C
0 1 2"
set alpha [ regexp -all -inline {[a-zA-Z]} $input]; #Match all alphabets
set numeric [ regexp -all -inline {\d} $input]; #Match all numeric values
#Using 'foreach' to loop both list at a same time.
foreach a $alpha n $numeric {
puts "$a : $n"
}
If the pair is not equally distributed, (i.e either alphabet or numeric value is missing) then they will be assigned with empty string during the course of foreach loop execution.
If you want to get them in Key-Value pair then, we can make use of dict or array in tcl.
Dictionary Implementation
foreach a $alpha n $numeric {
dict append test $a $n
}
puts "Whole dictionary : [ dict get $test ]"
puts "Value of key A : [ dict get $test A ]"; #Getting value of key 'A'
dict for { a n } $test {
puts "Key : $a ==> Value : $n "
}
Array Implementation
foreach a $alpha n $numeric {
set result($a) $n
}
puts "Whole array : [ array get result ]"
puts "Value of key A : $result(A) "#Getting value of key 'A'
foreach index [array names result] {
puts "Key : $index ==> Value : $result($index)"
}
Reference : dict, array