Wado

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:

  1. Overall precedence table: Which existing language to use as a baseline?
  2. Bitwise vs comparison operators: Which should have higher precedence?
  3. Bitwise NOT operator: Use ! (Rust) or ~ (C/Java/Python)?
  4. Increment/decrement operators: Include ++/-- or not?
  5. Power operator: Include ** or not?
  6. Comparison chaining: Allow a < b < c or 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:

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:

Language responses:

Power Operator

Python's ** has counterintuitive precedence:

-1**2  # = -1 (not 1!)
# Parsed as: -(1**2)

Language approaches:

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:

Decision

1. Use Rust's Precedence as Baseline

Wado adopts Rust's operator precedence table with minor modifications.

Rationale:

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:

4. No Increment/Decrement Operators

Wado does not have ++ or -- operators.

let mut count = 0;
count++;     // ❌ Compile error
count += 1;  // ✅ Correct

Rationale:

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:

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:

  1. Same-direction inequality: </<= can only chain with </<=, and >/>= can only chain with >/>=
  2. Equality chaining: == can only chain with ==
  3. No != chaining: != cannot be chained at all
  4. No mixing: Cannot mix equality operators with inequality operators
  5. 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:

  1. Mathematical intuition: Range checks like 0 <= x <= 100 are natural and common
  2. Familiar syntax: a < b < c reads as it does in Python. Python short-circuits it; Wado evaluates every operand
  3. Clearer intent: a < b < c is more readable than a < b && b < c
  4. Reject ambiguous cases: Mixed directions (a < b > c) are rarely intentional
  5. != is ambiguous: The meaning of a != b != c is 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:

Consequences

Positive

  1. Fixes C's design mistake: flags & MASK == VALUE works correctly without parentheses
  2. Avoids undefined behavior: No ++/-- operators
  3. Clear and explicit: f64::pow(x, y) instead of ambiguous **
  4. Familiar to C/Java/Python developers: ~ for bitwise NOT
  5. Mathematical comparison chaining: 0 <= x <= 100 reads as one range test, and is evaluated as one
  6. Rejects ambiguous chains: a < b > c and a != b != c are errors
  7. Consistent with Rust precedence: Minimal learning curve (except ~ and chaining)
  8. Battle-tested: Rust's precedence has been proven in production

Negative

  1. Diverges from Rust on bitwise NOT: Rust developers might use ! by habit
    • Mitigation: Compiler error will catch this immediately
  2. Diverges from Rust on comparison chaining: Rust rejects all chaining, Wado allows valid chains
    • Mitigation: Well-documented feature; clearer than Rust's blanket rejection
  3. 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
  4. != chaining is rejected: Some developers might expect a != b != c to work
    • Mitigation: Error message suggests alternatives like a != b && b != c or "all different" checks

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

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.

References

See also: docs/research-operator-precedence.md for detailed cross-language comparison.