0

I am using {*} in tcl for argument expansion and come across this issue.

#!/usr/bin/tclsh

set reset {
        set count 0;
        set age 24;
}

puts $reset

eval $reset; # This is working fine. Commented out the below line and tried

{*}$reset; # This is throwing error. Commented out the above line and tried.

if { [ info exists count ] && [ info exists age ] } {
        puts "count & age initialzed to  $count and $age"
} else {
        puts "count & age not initialzed :("
}

As you see, I have the reset variable defined and I am evaluating with eval as well as with {*}.

While eval is working fine, {*} is throwing error as

wrong # args: should be "set varName ?newValue?"
    while executing
"{*}$reset"

Then I have changed the variable reset as follows,

set reset {
            set count 0;
    }

i.e. I have removed the 2nd set statement and the code works fine.

Why {*} is working fine with one statement and not with more than that ? Or What I am missing here ?

Dinesh
  • 16,014
  • 23
  • 80
  • 122
  • Erm, even with the single command, it doesn't 'work' as such because `$count` will have the value `0;` instead of `0`, which actually explains the problem: the `;` is being interpreted as a string and not as a meta-character for the code. – Jerry Oct 14 '14 at 10:12
  • Yes. Jerry. You are right. Now only I am also observing that. Then at one instance, one command alone can be used ? – Dinesh Oct 14 '14 at 10:19
  • 1
    I wouldn't advise using the argument expansion like that because I don't think that's the reason why `{*}` was implemented. To evaluate code, use `eval` and to expand a list, use `{*}`. – Jerry Oct 14 '14 at 10:21
  • With two lines, it is like you are evaluating `set count "0;" set age "24;"`. – Jerry Oct 14 '14 at 10:22
  • Since `reset` really contains a script, `eval` is exactly the right thing to use. – Donal Fellows Oct 14 '14 at 12:39

1 Answers1

2

Commands lite eval and uplevel (and while and if etc) can evaluate scripts, i.e. strings/lists that consist of zero or more command invocations. When you invoke {*}$reset you get six words instead of one, but the interpreter still expects a single command invocation. What actually gets invoked is the command set with five arguments, and the command will balk at that.

Expanding a word into several words works as long as the expansion forms exactly one command invocation:

set foo bar
# => bar

Too few words:

set cmd set
# => set
{*}$cmd
# => wrong # args: should be "set varName ?newValue?"

Too many words:

set cmd {set foo baz ; set abc def}
# => set foo baz ; set abc def
{*}$cmd
# => wrong # args: should be "set varName ?newValue?"

Just right:

set cmd {set foo}
# => set foo
{*}$cmd
# => bar
set cmd {set foo baz}
# => set foo baz
{*}$cmd
# => baz

Also just right:

set cmd set
# => set
{*}$cmd ghi jkl
# => jkl
set cmd {set mno}
# => set mno
{*}$cmd pqr
# => pqr

Documentation: eval, if, set, {*}, uplevel, while

Peter Lewerin
  • 13,140
  • 1
  • 24
  • 27