重新定义TCL foreach

时间:2017-10-07 17:49:20

标签: foreach tcl

我想重新定义TCL' foreach,因为foreach的多个应用程序会破坏一些代码。

详细的问题描述

代码中断的实际情况发生在Vivado中,这是一个使用TCL编写脚本的程序。它们提供了一个指定get_<someObject>方法的API,它返回一个对象列表(实际上它的行为类似于列表,但由xilinx实现,以便以不同的方式处理缓存/打印)。要处理这些列表,我使用foreach。我第一次在列表中使用foreach时,我在循环变量中得到<objects>

但是,如果我在单个对象上应用foreach,则该对象将转换为表示其名称的字符串。问题是,API函数get_property需要一个对象。

在应用foreach两次时,似乎没有意义,如果您编写两个捕获对象列表并对其进行操作的函数,则可能会发生这种情况。

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
    }
}

如果我们现在调用这些函数如下

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

我询问是否可以在此question on the Xilinx Forums中修复此行为,但我正在寻找解决方法。

我想做什么

我想在此期间实施解决方法。虽然我能做到

a [list $o]
在b内部,我认为如果我可以重新定义foreach以不破坏上述情况会更好。这样,如果Xilinx可以修复行为,我可以简单地删除foreach的重新定义。 (我希望foreach能够正常工作,无论我有一个包含一个元素或单个元素的列表,据我所知,这在TCL中是相同的。)

我重新定义foreach的问题是:

  1. 我如何知道foreach是否被称为
    • foreach {varA varB} {valueList} {...}
    • foreach {varA varB} {valueList1 valueList2} {...}
  2. 有没有办法测试我是否有一个对象或包含一个对象的列表?想法是两个检测,如果它只是一个对象,如果是这样将它包装成一个列表,然后可以通过正常的foreach解包回对象,但是我不知道如何检测这种情况。
  3. 大纲代码我 喜欢写:

    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
    }
    

2 个答案:

答案 0 :(得分:3)

问题的根本原因是(使用你的例子)调用proc a,它需要一个列表,在proc b中调用时只有一个标量值。调用aa [list $o]的“解决方法”是解决方案。它将单个值转换为一个元素的列表。包含一个元素的列表与单个值不同。由于Tcl中的列表只是特殊格式的字符串,如果proc a的参数包含空格,它将被视为列表,分成空白分隔的组件。虽然Tcl足够灵活,可以让你从根本上重新定义语言,但我认为这只是“因为你可以,并不意味着你应该”。我只是不认为这种情况足够引人注目,因为一些代码重构会使问题消失。

答案 1 :(得分:2)

最终,问题在于您不希望将简单值视为列表。处理这种情况的方法之一确实是使用[list $a]从不应该被错误处理的值中列出一个列表,而另一种方法是更改​​a过程以获取多个参数这样您就可以在内部自动应用引用时将它们视为列表:

# 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]"
    }
}

然后你可以这样称呼它:

a $o

将列表传递给这样的过程时,您希望使用扩展语法:

a {*}[get_clock_regions X0Y0]

前导{*}是Tcl中的伪运算符,这意味着将参数的其余部分解释为列表,并将列表中的单词作为自己的参数传递。