Reputation: 4985
I would like to redefine TCL's foreach
, since multiple applications of foreach break some code.
The actual case where the code breaks happens in Vivado, a program using TCL for scripting. They supply an API that specifies get_<someObject>
methods, which returns a list of objects (actually something that behaves like a list, but is implemented by xilinx to handle caching/printing a bit differently). To handle these lists I use foreach
. The first time I use foreach
on the list, I get <objects>
in the loop variable.
However, if I apply foreach
on a single object, the object is converted into a string representing its name. The problem is, that the API function get_property
expects an object.
While applying foreach
twice, does not seem to make sense, this might happen if you write two functions that take a list of objects and operate on them.
i.e.
proc a {obj} {
puts "logging [llength $obj] objects:"
foreach o $obj {
puts "$o has column index [get_property COLUMN_INDEX $o]"
}
}
proc b {obj} {
foreach o $obj {
a $o
puts "working on $o"
get_property COLUMN_INDEX $o
}
}
If we now call these functions as follows
a [get_clock_regions X0Y0] # ok (the list is turned into single objects in foreach)
b [get_clock_regions X0Y0] # crashes inside a (the list from get_clock_regions is
# turned into objects by the foreach in b
# a is then called with a single object,
# the foreach now turns the object into a string
# representing the name, but get_property
# does not work on strings => error
I asked if the behaviour can be fixed in this question on the Xilinx Forums, however I am looking for a workaround for the meantime.
I would like to implement a workaround for the meantime. While I can do
a [list $o]
inside b, I think it would be nicer if I can redefine foreach
to not break the above case. This way, I can simply drop my redefinition of foreach
if Xilinx can fix the behaviour. (I am expecting foreach
to work the same regardless if I have a list with one element or a single element, to my understanding this is the same in TCL).
My issues with redefining foreach are:
foreach
is called as
foreach {varA varB} {valueList} {...}
foreach {varA varB} {valueList1 valueList2} {...}
Outline code I would like to write:
proc safeForeach {varnames valueLists body} {
if { thereAreMultiple valueLists } { # this issue no 1
foreach valueList $valueLists {
if {wouldDecayToString $valueLists} { # this is issue no 2
set valueLists [list $valueLists]
}
}
} else {
if {wouldDecayToString $valueLists} { # this is issue no 2 again
set valueLists [list $valueLists]
}
}
#the next line should be wraped in `uplevel`
foreach $varnames $valueLists $body
}
Upvotes: 1
Views: 558
Reputation: 137787
Ultimately, the issue is that you don't want to treat simple values as lists. One of the ways of dealing with that is indeed to use [list $a]
to make a list out of the value that shouldn't be mistreated, but another is to change the a
procedure to take multiple arguments so that you can treat them as a list internally while having quoting automatically applied:
# The args argument variable is special
proc a {args} {
puts "logging [llength $args] objects:"
foreach o $args {
puts "$o has column index [get_property COLUMN_INDEX $o]"
}
}
Then you can call it like this:
a $o
When passing in a list to a procedure like this, you want to use expansion syntax:
a {*}[get_clock_regions X0Y0]
That leading {*}
is a pseudo-operator in Tcl, which means to interpret the rest of the argument as a list and to pass the words in the list as their own arguments.
Upvotes: 2
Reputation: 1551
The root cause of the problem is (using your example) invoking proc a
, which expects a list, with only a single scalar value when it is called in proc b
. Your "workaround" of invoking a
as, a [list $o]
, is the solution. It converts a single value into a list of one element. A list containing one element is not the same as a single value. Since lists in Tcl are just specially formatted strings, if the argument to proc a
contains whitespace it will be treated as a list, split up into the whitespace separated components. Although Tcl is flexible enough to allow you to radically redefine the language, I think this is a case of "just because you can, doesn't mean you should." I just don't think this case is compelling enough since some code refactoring would make the problem disappear.
Upvotes: 3