Skip to content
VLSI Mentor

Tcl Part I · Module 4 · Operators and Expressions

Precedence, Parentheses, and Readability

When an expression contains more than one operator, something has to decide which happens first. That order is called precedence, and expr has a full table of it. This chapter gives you the table with a worked example for every level, so you can read an expression somebody else wrote and know what it computes. Then it argues that you should almost never rely on it in code you write yourself, and shows the one precedence trap that has been producing real bugs in C-family languages for forty years and produces exactly the same bug here.

Foundation12 min readTclPart IOperatorsPrecedenceReadability

Part I · Module 4 · Chapter 4.2 · Precedence, Parentheses, and Readability

1. The Problem

Azvya Education Pvt. Ltd.VLSI Mentor
classic.tcl
puts "2 + 3 * 4   = [expr {2 + 3 * 4}]"
puts "(2 + 3) * 4 = [expr {(2 + 3) * 4}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
2 + 3 * 4   = 14
(2 + 3) * 4 = 20

Multiplication happened first, so the answer is 14. That one you knew from school. The rest of the table is less familiar, and some of it is genuinely surprising.

2. The Order

From tightest binding to loosest:

LevelOperatorsNotes
1- + ~ ! (unary)binds tighter than ** — see 4.1 §5
2**right-associative
3* / %
4+ - (binary)
5<< >>
6< > <= >=
7== != eq ne in ni
8&bitwise and
9^bitwise exclusive or
10``
11&&
12`
13?:right-associative

Everything except ** and ?: groups left to right.

The two rows worth staring at are 1 and 7–10. Unary minus above ** is covered in Chapter 4.1 §5. The bitwise operators sitting below the comparisons is §5 of this chapter, and it is the one that bites.

3. The Ladder, Demonstrated

A table you cannot check is a table you half-remember. Here is one verified expression per level — in each case the answer is only possible under the stated order:

Azvya Education Pvt. Ltd.VLSI Mentor
ladder.tcl
puts "unary before **     -2 ** 2            = [expr {-2 ** 2}]"
puts "* before +           2 + 3 * 4          = [expr {2 + 3 * 4}]"
puts "+ before <<          1 << 2 + 1         = [expr {1 << 2 + 1}]"
puts "<< before <          1 << 2 < 8         = [expr {1 << 2 < 8}]"
puts "comparison before && 1 < 2 && 3 < 4     = [expr {1 < 2 && 3 < 4}]"
puts "&& before ||         0 || 1 && 0        = [expr {0 || 1 && 0}]"
puts "|| before ?:         0 || 1 ? 10 : 20   = [expr {0 || 1 ? 10 : 20}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
unary before **     -2 ** 2            = 4
* before +           2 + 3 * 4          = 14
+ before <<          1 << 2 + 1         = 8
<< before <          1 << 2 < 8         = 1
comparison before && 1 < 2 && 3 < 4     = 1
&& before ||         0 || 1 && 0        = 0
|| before ?:         0 || 1 ? 10 : 20   = 10

Work through two of them.

1 << 2 + 1 is 8. Addition binds tighter, so it is 1 << 3, which is 8. If shifting had happened first you would have 4 + 1, which is 5.

0 || 1 && 0 is 0. && binds tighter, so it is 0 || (1 && 0), which is 0 || 0. If || had gone first you would have (0 || 1) && 0, which is also 0 — so pick the other case to be sure: 1 || 0 && 0 is 1, because it is 1 || (0 && 0).

4. Left to Right, Except Twice

Azvya Education Pvt. Ltd.VLSI Mentor
associativity.tcl
puts "20 - 5 - 3    = [expr {20 - 5 - 3}]"
puts "20 / 5 / 2    = [expr {20 / 5 / 2}]"
puts "2 ** 3 ** 2   = [expr {2 ** 3 ** 2}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
20 - 5 - 3    = 12
20 / 5 / 2    = 2
2 ** 3 ** 2   = 512

The first two group left to right: (20 - 5) - 3 and (20 / 5) / 2. The third does not — 2 ** (3 ** 2), which is 512.

?: also associates to the right, which is what lets you chain it — Chapter 4.7.

5. The Trap: Comparison Binds Tighter Than Bitwise

This is the one to remember:

Azvya Education Pvt. Ltd.VLSI Mentor
bitwise-trap.tcl
puts "1 & 3 == 3   = [expr {1 & 3 == 3}]"
puts "(1 & 3) == 3 = [expr {(1 & 3) == 3}]"
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
1 & 3 == 3   = 1
(1 & 3) == 3 = 0

Two different answers from the same characters.

The first is 1 & (3 == 3), which is 1 & 1, which is 1. The second is what you almost certainly meant: mask 3 with 1, then test the result against 3 — which is false.

&, ^ and | all sit below == and != in the table. So any expression that masks a value and then compares it needs parentheses, every time.

6. Two Different Jobs

It is worth being explicit about why this chapter contains both a table and advice to ignore it.

Learn the table to read. You will open scripts written by people who relied on precedence. 1 << 2 + 1 is in real code, and you need to know it is 8.

Use parentheses to write. Your reader should not need the table. Parentheses cost two characters and remove an entire category of review question.

Azvya Education Pvt. Ltd.VLSI Mentor
write-this-way.tcl
set a 2 ; set b 3 ; set c 4

puts [expr {$a + $b * $c}]
puts [expr {$a + ($b * $c)}]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
14
14

Same answer. The second says so.

7. Long Expressions Belong on Several Lines

When an expression needs more than about one line of thought, the parentheses are treating a symptom:

Azvya Education Pvt. Ltd.VLSI Mentor
split.tcl
set width 1920
set height 1080
set bytes_per_pixel 3

set pixels [expr {$width * $height}]
set frame_bytes [expr {$pixels * $bytes_per_pixel}]
set frame_mb [expr {$frame_bytes / 1048576.0}]

puts [format "%.2f MB per frame" $frame_mb]
Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
5.93 MB per frame

Three named steps instead of one expression. Each line can be printed, checked and understood on its own, and the names say what the numbers are. That is usually worth more than brevity.

8. Common Mistakes

Masking and comparing without parentheses. §5. The most damaging one here.

Assuming ** groups left to right. §4.

Assuming -2 ** 2 is -4. Chapter 4.1 §5.

Trusting memory for the middle of the table. Levels 5 to 10 are the ones nobody recalls correctly. Parenthesise.

Parenthesising so heavily the expression becomes unreadable. §6.

9. Exercises

Exercise 1 — predict the ladder. Cover the output in §3 and predict all seven lines. Which did you get wrong?

Exercise 2 — the bitwise trap. Predict expr {5 & 7 == 7}, then check. Then write what you probably meant, confirm the two answers differ, and say which one a reader would assume.

Exercise 3 — add the parentheses. Rewrite each so the order is obvious without the table:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
expr {$a + $b * $c - $d}
expr {$x << 2 + 1}
expr {$flags & $mask == $mask}
expr {$a < $b && $c < $d || $e}

Exercise 4 — associativity. Predict expr {100 / 10 / 2} and expr {2 ** 2 ** 3}. Explain why they group differently.

Exercise 5 — split it up. Rewrite this as named steps, and say which version you would rather debug:

Azvya Education Pvt. Ltd.VLSI Mentor
Snippet
set result [expr {($total - $failed) * 100.0 / $total}]

Exercise 6 — find the real bug. A colleague's flag test always returns 1 for odd values of $flags. The line is expr {$flags & 1 == 1}. Explain what it computes and fix it.

10. Remember

  • Precedence decides which operator applies first. The full table is in §2.
  • Unary operators bind tighter than **, which is unusual.
  • ** and ?: associate to the right. Everything else groups left to right.
  • Bitwise &, ^ and | bind looser than == and !=. This is the trap.
  • expr {$flags & $MASK == $MASK} is not a flag test. Parenthesise the mask.
  • Learn the table to read other people's code; use parentheses so nobody needs it for yours.
  • Parenthesise where two kinds of operator meet, and leave plain arithmetic alone.
  • If an expression needs real thought, split it into named steps.

Next: comparison, and the question you did not mean to ask.

Where this fits

Part of the Tcl curriculum.