I just tried the following in tclsh:
proc pp {$ag} { puts hi}
pp whatever
To my surprise, this works fine! I had expected a syntax error because the proc argument is a variable. But it seems tcl takes it fine.
Then I experimented:
proc pp {$ag} { puts "hi $ag"}
pp whatever
and
proc pp {$ag} { puts "hi ${$ag}"}
pp whatever
I got error, can't read "ag": no such variable.
This makes me wondering, can proc argument be variable? If not, why not error out in the first script?
The first case works because you never use the parameter to pp.
When you invoke the proc command, the text of the invocation is evaluated just like in any other command invocation. In all the above cases, the third argument (which will become the argument list of the pp command) is is wrapped in braces, which means it won't be evaluated as a variable but as a string of three characters: "$ag" (i.e. the dollar sign is just a regular character here). This is not an error, and does work, just not the way you seem to expect it to.
It's a bit tricky to get the value of the parameter $ag, though. This works:
proc pp {$ag} {puts "hi [set {$ag}]"}
The dollar notation is actually just syntactic sugar for the unary set command. Sometimes the dollar notation won't work, and you need to fall back to an explicit set.
This does work too, though:
proc pp {$ag} {puts "hi ${$ag}"}
So, in your invocations, the third argument to proc isn't really a variable evaluation, it just looks like one. You can of course use an actual variable evaluation in the invocation of proc:
set foo {bar baz}
proc qux $foo {puts $bar ; puts $baz}
qux abc def
# => abc
# => def
What the Tcl interpreter really sees here is:
proc qux {bar baz} {puts $bar ; puts $baz}
Or you can go really crazy:
set foa {proc qux}
set fob {bar}
lappend fob baz
set foc {puts $bar}
set fod "puts \$baz"
{*}$foa $fob [join [list $foc $fod] { ; }]
Which amounts to the same thing as the previous invocation of proc. (If you don't believe me, try list {*}$foa $fob [join [list $foc $fod] { ; }])
This example just looks (and is) weird, but many times it's actually useful to construct new commands within your program, and in those cases it's really nice that the text used in the invocation of proc, like with any other command, is simply text that the evaluation rules of Tcl can be applied to. You can use any kinds of string or list operations on it and join up pieces of text from various sources, even user input (if you can trust it).
Documentation: Tcl evaluation rules including $ and {*}, join, lappend, list, proc, puts, set
Related
Is there any problem (e.g. a performance penalty) if the body of a proc is provided in quotes rather than curly brackets?
My code generates procs (as OOP-like methods) inside of other procs, e.g.
proc dataObject {name someData} {
# more stuff
proc ${name}.getData {args} \
"checkArgs \$args 0; return $someData"
# more stuff
}
and for simplity, I use quotes to enable variable substitution. It works, but I'm just worried that the code may not be precompiled or something.
Thanks for your help!
I'm just worried that the code may not be precompiled or something.
This is not to worry about, besides, there is no precompilation or similar in Tcl. Once executed for the first time, the generated proc's body will be byte-compiled (however the body script was assembled).
However, your proc generator is not robust. Variable substitution under quotes will break your body script when someData contains a string which renders the body script or one of its commands incomplete, e.g.:
dataObject test "do it"
will fail because it translates into
return do it;
There are several ways to assemble a script (command-sequence string) in a robust manner, one is using list protection:
proc dataObject {name someData} {
set procName ${name}.getData
append body {checkArgs $args 0} \;
append body [list return $someData] \;
proc $procName {args} $body
return [namespace which -command $procName]
}
As pointed out by Donal in another answer, nesting proc calls one in another is not necessarily leading to what you expect. In your case, though, as a generator, it might be acceptable. Still, you might want consider using a Tcl lambda or a proper (well, data) object?
It is difficult to do completely reliable code generation, but not impossible and using double quotes around a procedure body is entirely legal. I advise constraining the space of inserted words to non-empty alphanumeric prior to doing the codegen; that stops almost all mischief dead; other values need to be quoted (the list command can do exactly the right quoting you need with a little encouragement). It's often easier to generate an alias that curries some extra arguments onto a call of a non-varying procedure. Here's a very simple example of what I mean:
proc saySomething {a b} {
puts -nonewline $a
puts $b
}
proc makeSpeaker {cmd prefix} {
interp alias {} $cmd {} saySomething "[string trimright $prefix] "
}
makeSpeaker hello "Hello to"
hello Ralf
# ==> Hello to Ralf
As you can see, we've “generated” code that includes a word with a space in it without having to do complex quoting. It can't do everything, but it can do a lot.
And don't make your own pseudo-OO code. Not these days. Tcl from 8.6 onwards comes with an OO system core that makes doing that stuff much faster and more reliable.
oo::class create Speaker {
variable Prefix
constructor {prefix} {
set Prefix "[string trimright $prefix] "
}
method say {suffix} {
puts -nonewline $Prefix
puts $suffix
}
}
Speaker create greeting "Hello to"
greeting say Ralf
Of course, you can mix these two together to get some truly powerful approaches, but then the example's getting a bit long for quick comprehension...
Tcl manuals say that curly braces do not allow variable substitution.
However this works only with some commands but not with others.
What is the difference and how to identify the cases where the substitution will occur and the cases where it won't occur?
% set x 3
3
% puts {$x}
$x
% expr {$x}
3
Referring to the list of standard commands: any command that takes a "body" or "script" argument will eventually evaluate that body as code. With no guarantees about exhaustiveness:
after, apply, catch, eval, expr, fileevent (and chan event), for, foreach, if, interp eval, lmap, some namespace subcommands, some oo::* commands, proc, subst, switch, try, uplevel, while
This is truly one of Tcl's greatest strengths. It gives you the power to easily write your own control structures. For example, Tcl does not provide a do-while loop, but you can do this:
proc do {body while condition} {
if {$while ni {while until}} {
error "some message about usage..."
}
while true {
uplevel 1 $body
set status [uplevel 1 [list expr $condition]]
if {$while eq "while" && !$status} then break
if {$while eq "until" && $status} then break
}
}
so that
% set i 0; while {[incr i] < 3} {puts "$i"}
1
2
% set i 0; do {puts "$i"} while {[incr i] < 3}
0
1
2
% set i 0; do {puts "$i"} until {[incr i] == 3}
0
1
2
Some commands are explicitly described as treating an argument or arguments as a script or an expression; when evaluation of the script or expression happens (which might be immediately, or might be later, depending on the command) the substitutions described inside that string that is a script or expression are performed. (The subst command is a special case that can only apply a selected subset of substitutions.)
How do you know which is which? It depends on the command. Literally. Go and read the documentation. For example, in the documentation for catch we see:
SYNOPSIS
catch script ?resultVarName? ?optionsVarName?
DESCRIPTION
The catch command may be used to prevent errors from aborting command interpretation. The catch command calls the Tcl interpreter recursively to execute script, and always returns without raising an error, regardless of any errors that might occur while executing script. […]
In this case, we see that the first argument is always evaluated (immediately) as a Tcl script by calling the Tcl interpreter (or rather it's actually bytecode compiled in most cases, but that's an implementation detail).
Similarly, in the documentation for proc we see:
SYNOPSIS
proc name args body
DESCRIPTION
The proc command creates a new Tcl procedure named name, replacing any existing command or procedure there may have been by that name. Whenever the new command is invoked, the contents of body will be executed by the Tcl interpreter. […]
In this case, it's the body that is going to be evaluated as a script (“by the Tcl interpreter” is a form of language that means that) but later, when the procedure is called. (catch said nothing about that; by implication, it acts immediately.)
A third case is the documentation for while:
SYNOPSIS
while test body
DESCRIPTION
The while command evaluates test as an expression (in the same way that expr evaluates its argument). The value of the expression must a proper boolean value; if it is a true value then body is executed by passing it to the Tcl interpreter. […]
From this, we can see that the test argument is an expression (which uses expression rules) and body is a script.
If you want to create a substitution-free single-command script where you can use arbitrary values for everything (this perfect for setting up a callback) use the list command as that is defined to produce lists in canonical form, which happens (by design) to be exactly the form that single commands without substitution-surprises can take:
set xyz "123 456"
set callback [list puts $xyz]
set xyz {[crash bang wallop]}
puts "READY..."
eval $callback
I have searched for lappend $var1 $var2, but don't find any exact answer, how it will be executed.
% set a {a b c}
a b c
% set b {d e}
d e
% puts [lappend $c $b]
can't read "c": no such variable >>> here it throws error like variable not exist
% puts [lappend $a $b]
{d e} >>> here it doesn't throw any error, can someone explain it and how to print the value of $a, if $a is a new variable
% puts $$A
can't read "A": no such variable
% puts $$a
$a b c
% puts ${$a}
can't read "$a": no such variable
Tcl's got a two level syntax that it applies rigorously to everything. The first level is the Tcl generic syntax, which takes:
lappend $var1 $var2
and parses it out to three words: lappend, a word obtained by reading the variable var1, and a word obtained by reading the variable var2.
Then Tcl dispatches to the command named by the first word (lappend, a Tcl built-in) which applies command syntax handling. In the case of lappend, it's pretty simple: the first argument names a variable and the second and subsequent arguments are words to append to the list in the named variable.
In your case, the first argument that names a variable is obtained by reading another variable (var1) and the value to append to the list is coming from a variable (var2); a name like a b c d e is a legal variable name in Tcl, but it's really awkward to use. And the chance is very high that you don't want to write that: putting variable names in a variable is usually an indicator of confusing code. You can do it, but you hardly ever want to do it (except when you're using the variable name with upvar). You probably really meant to write:
lappend var1 $var2
Tcl is very exact about the distinction between variable names and variable contents. The $ is not decorative! It's there to say “read this variable, right now”, and $var1 is virtually equivalent to [set var1] in semantic terms. (The $ shorthand was later, a Tcl 2.0 feature from way back in the day!)
Tcl also doesn't allow double-dereferencing with $$vrbl. In the rare cases you need it, you do [set $vrbl]. And if you do that, you probably should immediately see if you can use an array instead as that's typically a better choice…
lappend's first parameter is a variable name, not a value. Therefore, in general, it should be:
lappend var1 $var2
where both var1 and var2 are list variables. See the Tcl lappend man page for more details.
I don't really understand how to use the eval arguments.
If the script I want to eval is :
set myscript { puts $::argv }
Then I want to call my script like this :
eval $myscript anArgument
And I expect the output to be "anArgument", but instead I have :
can not find channel named ""
while evaluating {eval $script vvv}
When you invoke eval, the command concatenates all its arguments and attempts to evaluate the resulting list (or string if you will, it's the same thing here). So, first the arguments { puts $::argv } and anArgument are concatenated into the list {puts $::argv anArgument}, and then the interpreter tries to evaluate that. If the value of the global variable argv is an empty list, the actual command invoked will be equivalent to puts {} anArgument. puts will try to use {} as a channel identifier to output to, fail and leave an error message.
Now, if what you wanted to do was to pass anArgument to myscript and then eval it as puts anArgument, you should instead write
set myscript {puts $myarg}
set myarg anArgument
eval $myscript
In the first line, the evaluation of $myarg is postponed because of the quoting braces which turn $ into a regular text character. The variable myarg is then set to a value (this can happen anywhere in the code as long as it comes before the eval). In the third line, the script is evaluated, and at that point the argument $myarg is replaced by the value anArgument, which is then printed.
The sort of invocation you attempted is possible, but then you need to use apply instead of eval, and a closure (anonymous function) instead of a script.
set myfunc {myarg {puts $myarg}}
apply $myfunc anArgument
The global variable argv does not pass arguments to a script which is passed to eval: when tclsh or wish is started by the operating system, any command line arguments given are placed in argv, and the value is never changed during execution unless you change it yourself (don't do that, it's just confusing).
Documentation: apply, eval, puts, set
argv is documented here.
What I want to do is parse an argument to a tcl proc as a string without any evaluation.
For example if I had a trivial proc that just prints out it's arguments:
proc test { args } {
puts "the args are $args"
}
What I'd like to do is call it with:
test [list [expr 1+1] [expr 2+2]]
And NOT have tcl evaluate the [list [expr 1+1] [expr 2+2]]. Or even if it evaluated
it I'd still like to have the original command line. Thus with the trivial "test"
proc above I'd like to be able to return:
the args are [list [expr 1+1] [expr 2+2]]
Is this possible in tcl 8.4?
You cannot do this with Tcl 8.4 (and before); the language design makes this impossible. The fix is to pass in arguments unevaluated (and enclosed in braces). You can then print them however you like. To get their evaluated form, you need to do this inside your procedure:
set evaluated_x [uplevel 1 [list subst $unevaluated_x]]
That's more than a bit messy!
If you were using Tcl 8.5, you'd have another alternative:
set calling_code [dict get [info frame -1] cmd]
The info frame -1 gets a dictionary holding a description of the current command in the context that called the current procedure, and its cmd key is the actual command string prior to substitution rules being applied. That should be about what you want (though be aware that it includes the command name itself).
This is not available for 8.4, nor will it ever be backported. You might want to upgrade!
When passing the arguments into test, enclose them in braces, e.g.:
test {[list [expr 1+1] [expr 2+2]]}