0

I have a problem in Tcl 8.4.

The concept of what I want to do is something like this:

  • an existing built-in function, builtinFn, has one required argument and then a variable number of arguments (the number actually required depends on the first argument), so it could be called like builtinFn {arg_1a arg_1b} "two" "three" "four", say;
  • I want to provide a wrapping function, wrapperFn, that examines the first argument and then decomposes it into its constituent parts (arg_1a and arg_1b in the example above), and then calls builtinFn with each individual constituent of the first argument separately, as in builtinFn {arg_1a} "two" "three" "four" and builtinFn {arg_1b} "two" "three" "four".

No matter what I have tried, I get an error message, "Error: wrong # args:" when I call builtinFn from within wrapperFn.

Simplified example of the problem

proc myInternal {var1 args} { 
mess "var1 is $var1 (length is [llength $var1]), and args is $args  (length is [llength $args]).  \n" turquoise4;
}
proc myWrapper {var1 args} { 
mess "var1 is $var1 (length is [llength $var1]), and args is $args  (length is [llength $args]).  \n" green4; 
myInternal $var1 $args
}

Note: wherever you see mess please read it as analogous to puts — it just writes to the console in a neat way, with the option of specifying the colour of the text.

If I call myInternal one two three, then the output is

var1 is one (length is 1), and args is two three  (length is 2).  

If I call myWrapper one two three, then the output is

var1 is one (length is 1), and args is two three  (length is 2).  
var1 is one (length is 1), and args is {two three}  (length is 1).  

I have seen some posts that might be somehow relevant.

Maybe this could be solved with {*} in Tcl 8.5, but that is not available to me currently. I haven't figured out how to implement it with any other option such as list or concat or eval.

Some other things I have tried that did not work:

proc myWrapper2 {var1 args} { 
mess "var1 is $var1 (length is [llength $var1]), and args is $args  (length is [llength $args]).  \n" green4; 
myInternal "$var1 $args"
}
myWrapper2 one two three
# Output:  
# var1 is one (length is 1), and args is two three  (length is 2).  
# var1 is one two three (length is 3), and args is   (length is 0).  

proc myWrapper3 {var1 args} { 
mess "var1 is $var1 (length is [llength $var1]), and args is $args  (length is [llength $args]).  \n" green4; 
myInternal $var1 [split $args]
}
myWrapper3 one two three
# Output:  
# var1 is one (length is 1), and args is two three  (length is 2).  
# var1 is one (length is 1), and args is {two three}  (length is 1).  

proc myWrapper4a {var1 args} { 
mess "var1 is $var1 (length is [llength $var1]), and args is $args  (length is [llength $args]).  \n" green4; 
myInternal [list $var1 $args]
}
myWrapper4a one two three
# Output:  
# var1 is one (length is 1), and args is two three  (length is 2).  
# var1 is one {two three} (length is 2), and args is   (length is 0).  

proc myWrapper4b {var1 args} { 
mess "var1 is $var1 (length is [llength $var1]), and args is $args  (length is [llength $args]).  \n" green4; 
myInternal [concat $var1 $args]
}
myWrapper4b one two three
# Output:  
# var1 is one (length is 1), and args is two three  (length is 2).  
# var1 is one two three (length is 3), and args is   (length is 0).  

proc myWrapper5 {var1 args} { 
mess "var1 is $var1 (length is [llength $var1]), and args is $args  (length is [llength $args]).  \n" green4; 
myInternal [puts "$var1 $args"]
}
myWrapper5 one two three
# Output:  
# var1 is one (length is 1), and args is two three  (length is 2).  
# var1 is  (length is 0), and args is   (length is 0).  

proc myWrapper6 {var1 args} { 
mess "var1 is $var1 (length is [llength $var1]), and args is $args  (length is [llength $args]).  \n" green4; 
eval myInternal $var1 [split $args ,]
}
myWrapper6 one two three
# Output:  
# var1 is one (length is 1), and args is two three  (length is 2).  
# var1 is one (length is 1), and args is {two three}  (length is 1).  

proc myWrapper7 {var1 args} { 
mess "var1 is $var1 (length is [llength $var1]), and args is $args  (length is [llength $args]).  \n" green4; 
eval myInternal [split "$var1 $args" ,]
}
myWrapper7 one two three
# Output:  
# var1 is one (length is 1), and args is two three  (length is 2).  
# var1 is one two three (length is 3), and args is   (length is 0).  

proc myWrapper8 {var1 args} { 
mess "var1 is $var1 (length is [llength $var1]), and args is $args  (length is [llength $args]).  \n" green4; 
eval {myInternal [split "$var1 $args" ,]}
}
myWrapper8 one two three
# Output:  
# var1 is one (length is 1), and args is two three  (length is 2).  
# var1 is {one two three} (length is 1), and args is   (length is 0).  

proc myWrapper9 {var1 args} { 
mess "var1 is $var1 (length is [llength $var1]), and args is $args  (length is [llength $args]).  \n" green4; 
eval [linsert {} 0 myInternal $var1 $args]
}
myWrapper9 one two three
# Output:  
# var1 is one (length is 1), and args is two three  (length is 2).  
# var1 is one (length is 1), and args is {two three}  (length is 1).  

In case it wasn't obvious, I need the second statement, which is written by myInternal (invoked from myWrapper), to exactly match the first statement, which is written 'directly' by myWrapper.

Thanks for any tips!

—DIV

DIV
  • 33
  • 4
  • 1
    Just so as you know, Tcl 8.4 is _entirely_ out of support. 8.5 is considered the absolute minimum, and it's recommended that code created now use 8.6 (unless they're brave enough to try development versions like the 8.7 and 9.0 alphas). – Donal Fellows Feb 24 '21 at 12:20
  • Thanks, Donal. If it were up to me I'd be interested in using a later version of Tcl, but this Tcl version is tied to a larger application that is in 'maintenance-only' support: the latest version of the application (released in the middle of 2021) is still using Tcl version 8.4.11. – DIV Oct 12 '21 at 05:24

2 Answers2

3

As you gathered, in Tcl 8.4 you need to use eval for this. To be reasonably safe that no side effects happen, you need to pass a list to eval consisting of the name of the command, followed by each individual argument. One way to achieve this for your example is:

eval [linsert $args 0 myInternal $var1]

In all your attempts you kept on passing args as a single element, or made things even worse.

A method using concat could be:

eval [concat myInternal [list $var1] $args]

Note that you have to use list around $var1 here to prevent any special characters it may contain from causing havoc. For example:

myWrapper "hello world" two three
myWrapper \\{ two three
Schelte Bron
  • 4,113
  • 1
  • 7
  • 13
  • Ah, now I think I see where I went wrong. In my attempt with `myWrapper9` I was effectively doing `linsert {} 0 {1a 1b} {2 3}`, which yields `{1a 1b} {2 3}`. So the order was correct, and the integrity of `var1` was preserved, but I didn't break apart the `{2 3}`. What I should have done was like `linsert {2 3} 0 {1a 1b} `, which would yield `{1a 1b} 2 3`. – DIV Feb 24 '21 at 23:52
  • And in your alternative using `concat`, for example `concat myInternal [list {1a 1b}] {2 3}` yields `myInternal {1a 1b} 2 3`, which also looks right. – DIV Feb 25 '21 at 00:18
0

Building on Schelte Bron's answer, the wrapperFn would be

proc wrapperFn {varlist args} {
    foreach var $varlist {
        eval [linsert $args 0 builtinFn $var]
    }
}
glenn jackman
  • 238,783
  • 38
  • 220
  • 352