WEP: Operator Precedence and Associativity
Context
Wado needs to define operator precedence and associativity rules. The language aims to minimize learning costs while avoiding design mistakes present in older languages. Several key decisions were needed:
- Overall precedence table: Which existing language to use as a baseline?
- Bitwise vs comparison operators: Which should have higher precedence?
- Bitwise NOT operator: Use
!(Rust) or~(C/Java/Python)? - Increment/decrement operators: Include
++/--or not? - Power operator: Include
**or not? - Comparison chaining: Allow
a < b < cor make it an error?
C's Design Mistake
C has a well-known precedence design flaw: bitwise operators (&, |, ^) have lower precedence than comparison operators (==, <, etc.). This causes bugs:
// C/Java/JavaScript - Bug!
if (flags & MASK == EXPECTED) // Parsed as: flags & (MASK == EXPECTED)
// Intended: (flags & MASK) == EXPECTED
Historical reason: Early C had no &&/||. When they were added, &/| precedence was left unchanged for backward compatibility, creating a permanent design flaw.
Impact: Requires excessive parentheses and is counterintuitive since & and | conceptually behave like arithmetic operators:
a + b == 7correctly parses as(a + b) == 7✅a & b == 7incorrectly parses asa & (b == 7)❌
Language Comparison
| Language | Bitwise vs Comparison | Fixed C's Mistake? |
|---|---|---|
| C/Java/JS | Comparison > Bitwise | ❌ No |
| Rust | Bitwise > Comparison | ✅ Yes |
| Go | Bitwise > Comparison | ✅ Yes |
| Python | Comparison > Bitwise | ❌ No |
Rust and Go fixed this by giving bitwise operators higher precedence than comparison operators.
Increment/Decrement Operators
++/-- cause serious issues:
int x = 1;
int y = x + ++x; // Undefined behavior!
// GCC: y = 4
// Clang: y = 3
Problems:
- Undefined behavior when used multiple times in same expression
- Side effect confusion
- Prefix vs postfix complexity
Language responses:
- Rust: Removed entirely ✅
- Python: Never had them ✅
- Go: Postfix only, statements only (not expressions) ⚠️
- C/Java/JS: Keep them (legacy) ❌
Power Operator
Python's ** has counterintuitive precedence:
-1**2 # = -1 (not 1!)
# Parsed as: -(1**2)
Language approaches:
- Python/JS: Have
**operator (with precedence quirks) ⚠️ - C/Java/Rust/Go: Use function:
pow(),Math.pow(),.pow()✅
Comparison Chaining
Different languages handle chained comparisons differently:
// JavaScript - Bug prone
1 < 2 < 3; // true (seems right)
3 > 2 > 1; // false (wait, what?)
// Evaluates as: (3 > 2) > 1 → true > 1 → 1 > 1 → false
Language approaches:
- C/Java/Go/JS: Left-associative (allows confusing bugs) ❌
- Rust: Non-associative (compile error) ✅
- Python: Special chaining syntax (
a < b < c=a < b and b < c, short-circuiting) ✅
Decision
1. Use Rust's Precedence as Baseline
Wado adopts Rust's operator precedence table with minor modifications.
Rationale:
- Fixes C's bitwise precedence mistake
- No
++/--(avoids undefined behavior) - Well-designed and battle-tested
- Minimizes learning costs (developers familiar with Rust can transfer knowledge)
2. Bitwise Operators Have Higher Precedence Than Comparison
Following Rust and Go, bitwise operators (&, |, ^) have higher precedence than comparison operators (==, <, etc.).
// Wado - Correct precedence
if flags & MASK == EXPECTED { // Parsed as: (flags & MASK) == EXPECTED ✅
// ...
}
Rationale: Aligns with arithmetic operator intuition, reduces need for parentheses.
3. Use ~ for Bitwise NOT
Wado uses ~ (tilde) for bitwise NOT, not ! (Rust) or ^ (Go).
let x = 0b1010;
let y = ~x; // Bitwise NOT
Rationale:
- More familiar to developers from C/Java/Python/JavaScript backgrounds
- Clear visual distinction between logical NOT (
!) and bitwise NOT (~) - Same precedence level (unary) as Rust's
!, so no precedence issues - Compiler will catch mistakes if Rust developers use
!instead
4. No Increment/Decrement Operators
Wado does not have ++ or -- operators.
let mut count = 0;
count++; // ❌ Compile error
count += 1; // ✅ Correct
Rationale:
- Avoids undefined behavior
- Eliminates side effect confusion
- Consistent with Rust and Python
x += 1andx -= 1are clear and unambiguous
Lexer behavior: The lexer detects ++ and -- tokens and produces an error, preventing expressions like a--b or a---b from being parsed incorrectly.
5. No Power Operator
Wado does not have a ** power operator. Use f64::pow or f32::pow
instead.
let result = x ** 2.0; // ❌ Compile error
let result = f64::pow(x, 2.0); // ✅ Correct
Rationale:
- Wasm has no native power instruction (would compile to function call anyway)
- Avoids precedence ambiguity (Python's
-1**2 = -1is counterintuitive) - Explicit function call is clearer
- Consistent with Rust, Go, C, and Java
6. Comparison Operator Chaining: Mathematical Chaining
Wado supports mathematical comparison chaining: Python's syntax, with stricter rules and no short-circuit.
Valid chains (same direction):
a < b < c // ✅ OK: a < b AND b < c (ascending)
a > b > c // ✅ OK: a > b AND b > c (descending)
a <= b <= c // ✅ OK: a <= b AND b <= c
a >= b >= c // ✅ OK: a >= b AND b >= c
a == b == c // ✅ OK: a == b AND b == c
Invalid chains (parse error):
a < b > c // ❌ Parse error: mixed directions
a > b < c // ❌ Parse error: mixed directions
a < b >= c // ❌ Parse error: mixing < and >=
a == b < c // ❌ Parse error: mixing == and inequality
a != b != c // ❌ Parse error: != chaining not allowed
Chaining Rules:
- Same-direction inequality:
</<=can only chain with</<=, and>/>=can only chain with>/>= - Equality chaining:
==can only chain with== - No
!=chaining:!=cannot be chained at all - No mixing: Cannot mix equality operators with inequality operators
- No short-circuit: every operand is evaluated once, left to right, and the comparisons join with
&. A chain is one test: a range test where the operators are inequalities, an all-equal test where they are==. Write&&where an operand must not run on some path.
Rationale:
- Mathematical intuition: Range checks like
0 <= x <= 100are natural and common - Familiar syntax:
a < b < creads as it does in Python. Python short-circuits it; Wado evaluates every operand - Clearer intent:
a < b < cis more readable thana < b && b < c - Reject ambiguous cases: Mixed directions (
a < b > c) are rarely intentional !=is ambiguous: The meaning ofa != b != cis unclear (is it "a, b, c are all different" or "a != b AND b != c"?)
Implementation: The parser checks each operator as it extends a comparison chain, and rejects the first one that breaks either rule:
- All operators in the chain are in the same "group" (ascending, descending, or equality)
!=is never chained
Consequences
Positive
- Fixes C's design mistake:
flags & MASK == VALUEworks correctly without parentheses - Avoids undefined behavior: No
++/--operators - Clear and explicit:
f64::pow(x, y)instead of ambiguous** - Familiar to C/Java/Python developers:
~for bitwise NOT - Mathematical comparison chaining:
0 <= x <= 100reads as one range test, and is evaluated as one - Rejects ambiguous chains:
a < b > canda != b != care errors - Consistent with Rust precedence: Minimal learning curve (except
~and chaining) - Battle-tested: Rust's precedence has been proven in production
Negative
- Diverges from Rust on bitwise NOT: Rust developers might use
!by habit- Mitigation: Compiler error will catch this immediately
- Diverges from Rust on comparison chaining: Rust rejects all chaining, Wado allows valid chains
- Mitigation: Well-documented feature; clearer than Rust's blanket rejection
- Comparison chaining complicates the parser: it must track the chain's group while it parses one
- Mitigation: Clear error messages guide developers to fix invalid chains
!=chaining is rejected: Some developers might expecta != b != cto work- Mitigation: Error message suggests alternatives like
a != b && b != cor "all different" checks
- Mitigation: Error message suggests alternatives like
Precedence Table for Wado
From highest to lowest precedence:
| Level | Operators | Associativity | Description |
|---|---|---|---|
| 1 | ::, ., () |
Left-to-right | Paths, calls, fields |
| 2 | ? |
N/A | Error propagation |
| 3 | ~, -, *, &, &mut |
Right-to-left | Unary operators |
| 4 | as |
Left-to-right | Type cast |
| 5 | *, /, % |
Left-to-right | Multiplicative |
| 6 | +, - |
Left-to-right | Additive |
| 7 | <<, >> |
Left-to-right | Bitwise shift |
| 8 | & |
Left-to-right | Bitwise AND |
| 9 | ^ |
Left-to-right | Bitwise XOR |
| 10 | \| |
Left-to-right | Bitwise OR |
| 11 | matches { pattern } |
Left (self-delimiting) | Pattern test (postfix) |
| 12 | ! |
Right-to-left | Logical NOT (unary) |
| 13 | ==, <, >, <=, >= (left-assoc with rules), != (no chain) |
Restricted | Comparison |
| 14 | && |
Left-to-right | Logical AND |
| 15 | \|\| |
Left-to-right | Logical OR |
| 16 | ..<, ..= |
N/A | Range operators |
| 17 | =, +=, -=, etc. |
Right-to-left | Assignment |
Key Differences from Rust
- Level 3:
~is bitwise NOT (Rust uses!for both). Logical!is a separate operator at Level 12, looser than the other unary operators. - Level 4:
assits between unary (level 3) and multiplicative (level 5), so*x as Tparses as(*x) as T— same as Rust. - Levels 11–12:
matchesis a postfix operator and!is a unary operator, but both bind below the binary operators. Rust has neither placement (it has nomatches, and its!is a tight unary). - Level 13: Comparison chaining allowed with semantic validation:
- Same-direction chains OK:
a < b < c,a > b > c,a == b == c - Mixed-direction chains rejected:
a < b > c !=cannot be chained- Rust rejects all comparison chaining at parse level
- Same-direction chains OK:
matches (Level 11) and logical ! (Level 12)
matches binds looser than the binary operators, as, and the value-producing
unary operators, and tighter than logical !. Its { pattern } right-hand side
is self-delimiting; the scrutinee side is left-associative.
!x matches { P }is!(x matches { P })— "xdoes not matchP".*x matches { P },x as T matches { P },a + b matches { P }, andflags & MASK matches { P }need no parentheses; a comparison, range, or assignment scrutinee does ((a == b) matches { P }).!binds tighter than comparison and&&/||, so!a == bis(!a) == b.
References
- Operator precedence is broken - foonathan.net
- C Operator Precedence - cppreference.com
- Rust Reference - Expressions
- Go 101 - Common Operators
- Python Expressions Documentation
- MDN - JavaScript Operator Precedence
- Learn C++ - Increment/decrement operators and side effects
See also: docs/research-operator-precedence.md for detailed cross-language comparison.
