tcl Expressions The problems with unbraced expressions


It is a good practice to provide expression string arguments as braced strings. The heading "Double Substitution" outlines important reasons behind the same.

The expr command evaluates an operator-based expression string to calculate a value. This string is constructed from the arguments in the invocation.

expr 1 + 2    ; # three arguments
expr "1 + 2"  ; # one argument
expr {1 + 2}  ; # one argument

These three invocations are equivalent and the expression string is the same.

The commands if, for, and while use the same evaluator code for their condition arguments:

if {$x > 0} ...
for ... {$x > 0} ... ...
while {$x > 0} ...

The main difference is that the condition expression string must always be a single argument.

As with every argument in a command invocation in Tcl, the contents may or may not be subjected to substitution, depending on how they are quoted / escaped:

set a 1
set b 2
expr $a + $b   ; # expression string is {1 + 2}
expr "$a + $b" ; # expression string is {1 + 2}
expr \$a + \$b ; # expression string is {$a + $b}
expr {$a + $b} ; # expression string is {$a + $b}

There is a difference in the third and fourth cases as the backslashes / braces prevent substitution. The result is still the same, since the evaluator inside expr can itself perform Tcl variable substitution and transform the string to {1 + 2}.

set a 1
set b "+ 2"
expr $a $b   ; # expression string is {1 + 2}
expr "$a $b" ; # expression string is {1 + 2}
expr {$a $b} ; # expression string is {$a $b}: FAIL!

Here we get into trouble with the braced argument: when the evaluator in expr performs substitutions, the expression string has already been parsed into operators and operands, so what the evaluator sees is a string consisting of two operands with no operator between them. (The error message is "missing operator at _@_ in expression "$a _@_$b"".)

In this case, variable substitution before expr was called prevented an error. Bracing the argument prevented variable substitution until expression evaluation, which caused an error.

Situations like this can occur, most typically when an expression to evaluate is passed in as a variable or parameter. In those cases there is no other choice than to leave the argument unbraced to allow the argument evaluator to "unpack" the expression string for delivery to expr.

In most other cases, though, bracing the expression does no harm and indeed can avert a lot of problems. Some examples of this:

Double substitution

set a {[exec make computer go boom]}
expr $a      ; # expression string is {[exec make computer go boom]}
expr {$a}    ; # expression string is {$a}

The unbraced form will perform the command substitution, which is a command that destroys the computer somehow (or encrypts or formats the hard disk, or what have you). The braced form will perform a variable substitution and then try (and fail) to make something of the string "[exec make computer go boom]". Disaster averted.

Endless loops

set i 10
while "$i > 0" {puts [incr i -1]}

This problem affects both for and while. While it seems that this loop would count down to 0 and exit, the condition argument to while is actually always 10>0 because that was what the argument was evaluated to be when the while command was activated. When the argument is braced, it is passed to the while command as $i>0, and the variable will be substituted once for every iteration. Use this instead:

while {$i > 0} {puts [incr i -1]}

Total evaluation

set a 1
if "$a == 0 && [incr a]" {puts abc}

What is the value of a after running this code? Since the && operator only evaluates the right operand if the left operand is true, the value should still be 1. But actually, it's 2. This is because the argument evaluator has already performed all variable and command substitutions by the time the expression string is evaluated. Use this instead:

if {$a == 0 && [incr a]} {puts abc}

Several operators (the logical connectives || and &&, and the conditional operator ?:) are defined to not evaluate all their operands, but they can only work as designed if the expression string is braced.