ilya1725
ilya1725

Reputation: 4938

How to call a procedure with arguments from argument list

This is Tcl 8.4.

I'm trying to make a procedure that can do something common on any other procedure specified by the user.

For example:

proc process_menu_item {command args {count 1}} {
    set inf     [expr {$count == -1 ? 1 : 0}]
    set end_str [expr {$count == -1 ? "inf" : $count}]
    set loop_cnt 0

    while {$inf || $count > 0} {
        incr loop_cnt
        puts [format "------- %s -------" [bold "LOOP $loop_cnt of $end_str"]]

        $command $args
    }
    return
}

This procedure, I hoped, will just execute the specified $command $count times. The problem I run into is with passing the arguments.

Let's assume I want to call a procedure like this:

proc example {{par_a {-1}} {par_b {-1}} {par_c 1}} {
    puts "All params: $par_a $par_b $par_c"
}

How should I call process_menu_item? Is it even possible? I tried a string, a list, but eventually par_a gets a big list of all the arguments.

Thank you.

Upvotes: 2

Views: 15234

Answers (3)

glenn jackman
glenn jackman

Reputation: 246807

I'm not able to give a more comprehensive answer right now, but interp alias might get you where you want to go. There's an example at rosettacode.org. If that doesn't fit, perhaps explore command traces -- those fire for every command and you can make choices based on the command name.

Upvotes: 0

Donal Fellows
Donal Fellows

Reputation: 137567

(First off, the args parameter to a procedure is only magical when it is last. Put anything after it and it becomes just an ordinary variable. Just so you know. Of course, that might even be what you need here!)

To call out of process_menu_item, you'd want to change:

$command $args

to this (Tcl 8.4 or earlier):

eval $command $args

Well, if you are paranoid you actually write this:

eval [list $command] [lrange $args 0 end]

Or you use Tcl 8.5 (or later):

$command {*}$args

Then, you'd probably build the call to process_menu_item like this:

process_menu_item frobnicate [list foo bar]

There are a few other ways to do it rather than using list (many Tcl commands can produce lists) or you could use a literal:

process_menu_item frobnicate {foo bar}

What isn't recommended is putting anything in that literal refers to variables inside process_menu_item (which would only work with the shortest eval-based form). It lets you do awesome stuff, but it's also incredibly fragile. If you want to pass in the loop variable, that's quite possible but it requires techniques that are a little more advanced (and you should ask another question).

Upvotes: 4

bta
bta

Reputation: 45057

You can always construct a wrapper function:

proc wrapper {arg_list} {
    example [lindex $arg_list 0] [lindex $arg_list 1] [lindex $arg_list 2]
}

TCL 8.5 has the {*} operator which was designed for this sort of thing:

% proc test {a b c} {
    puts a
    puts b
    puts c
}
%
% set inp [list "A" "B" "C"]
A B C
% test $inp
wrong # args: should be "test a b c"
% test {*}$inp
a
b
c

Upvotes: 2

Related Questions