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
puts "2 + 3 * 4 = [expr {2 + 3 * 4}]"
puts "(2 + 3) * 4 = [expr {(2 + 3) * 4}]"2 + 3 * 4 = 14
(2 + 3) * 4 = 20Multiplication 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:
| Level | Operators | Notes |
|---|---|---|
| 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:
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}]"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 = 10Work 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
puts "20 - 5 - 3 = [expr {20 - 5 - 3}]"
puts "20 / 5 / 2 = [expr {20 / 5 / 2}]"
puts "2 ** 3 ** 2 = [expr {2 ** 3 ** 2}]"20 - 5 - 3 = 12
20 / 5 / 2 = 2
2 ** 3 ** 2 = 512The 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:
puts "1 & 3 == 3 = [expr {1 & 3 == 3}]"
puts "(1 & 3) == 3 = [expr {(1 & 3) == 3}]"1 & 3 == 3 = 1
(1 & 3) == 3 = 0Two 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.
set a 2 ; set b 3 ; set c 4
puts [expr {$a + $b * $c}]
puts [expr {$a + ($b * $c)}]14
14Same 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:
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]5.93 MB per frameThree 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:
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:
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.
