# stringIsList {ab c d} => yes # stringIsList {ab {*}c d} => no proc stringIsList s { expr {[catch {llength $s}] ? no : yes} } # findClosingBrace {[list a b] c d e} => {[list a b]} # findClosingBrace {{a b} c d} => {{a b}} # findClosingBrace {$abc def ghi} => {$abc} # findClosingBrace {abc def ghi} => {abc} proc findClosingBrace str { array set closing [list \[ \] \{ \} \" \"] switch -exact -- [string index $str 0] { \[ - \{ - \" { set i 0 set closingBrace $closing([string index $str 0]) set i [string first $closingBrace $str] while {![info complete [string range $str 0 $i]]} { set i [string first $closingBrace $str [incr i]] } if {$i < 0} then { return -code error [list unmatched delimiter on $str] } string range $str 0 $i } default { set l [regexp -inline {^[$]?[[:alnum:]_]*(?:[[:space:]]|$)} $str] string trimright [lindex $l 0] } } } # expandStar {abc def} => {{abc def}} # expandStar {c {*}d e c {*}d e} => {{c } d { e c } d { e}} proc expandStar {line {i 0}} { set i [string first "{*}" $line $i] if {$i < 0 || [stringIsList $line]} then { list $line } else { set result {} set i0 [expr {$i - 1}] set first [string range $line 0 $i0] lappend result $first set i3 [expr {$i + 3}] set expr [findClosingBrace [string range $line $i3 end]] lappend result $expr set iRest [expr {$i3 + [string length $expr]}] set rest [string range $line $iRest end] eval lappend result [expandStar $rest] } } # expandCommandLine {abc def} => {abc def} # expandCommandLine {c {*}d e} => {eval [list c] d [list e]} # expandCommandLine {ab [c {*}d e] e f} => {ab [eval [list c] d [list e]] e f} proc expandCommandLine line { if {[string first {{*}} $line] < 0} then { return $line } regexp {^[[:space:]]*} $line result append result eval set i [string first \[ $line] if {$i < 0} then { foreach {a b} [expandStar $line] { set a [string trim $a] if {$a ne ""} then { append result " \[list " $a "\]" } append result " " $b } string trimright $result } else { set line1 [string range $line 0 [expr {$i - 1}]] set middle [findClosingBrace [string range $line $i end]] set l [string length $middle] set expr [string range $middle 1 end-1] append line1 \[ [expandCommandLine $expr] \] set rest [string range $line [expr {$i + $l}] end] append line1 [expandCommandLine $rest] set result "" foreach {a b} [expandStar $line1] { set a [string trim $a] if {$a ne ""} then { append result " \[list " $a "\]" } append result " " $b } string trimright $result } } proc explodeLines lines { set result {} set currentLine "" foreach line [split $lines \n] { append currentLine \n $line if {[info complete $currentLine]} then { lappend result [string trimleft $currentLine \n] set currentLine "" } } set result } proc xproc {name arglist body} { set expandedLines {} foreach line [explodeLines $body] { lappend expandedLines [expandCommandLine $line] } uplevel [list proc $name $arglist [join $expandedLines \n]] }
sourceCode is a simplified proc-inspector:
proc sourceCode p { list proc $p [info args $p] [info body $p] }This little test proc shows us how to do it:
xproc test1 arg1 { list first element {*}$arg1 last element }Now watch the result:
% sourceCode test1 proc test1 arg1 { eval [list list first element] $arg1 [list last element] } %The {*} construction has been replaced by an appropriate eval construction.Btw, is there any explanation for dummies how to tell my [Emacs speedbar] to handle xproc as well as proc?
JRW: Hi, I stumbled upon this article after I ran into a similar issue. I came up with a work-around that seems to function just fine for the cases I've thrown at it, though I wont say its full-proof.Here is my quick Proc, Enjoy!
# <p> # <br> Replacement for TCL 8.5's {*} operator for any TCL command # <br> Expands any item starting with a '*': command *[list A B C] => command A B C # <br> # <br> Example: # <br> set putArgs [list -nonewline stdout] # <br> set output "putArgs is expanded but this string is not" # <br> # <br> expand puts *$putArgs $output # <br> # <br> Further Considerations: # <br> Expand calls the procedure in the calling stack (using uplevel) so all upvar variables are retained # </p> # <p> # <br> Known Bugs: # <br> - Bug: If an argument is passed whose value starts with "*" it will attempt to expand it even if this was unintended # <br> Workaround: Suggest putting a " " or some other character as the first value in the passed string if potentially unintended # <br> If the input arguments should never start with "*" then there should not be a problem # </p> # # @author JRW # @since 01/19/2015 # @param command The TCL Command to execute # @param args The arguments for command as they would normally be arranged, except any parameter starting with '*' gets expanded # @return various The return value from the given command proc expand {command args} { foreach arg $args { if {[string index $arg 0] == "*"} { append command " [lrange [string trimleft $arg *] 0 end] " } else { lappend command $arg } } return [uplevel $command] }