What is EK9's operator precedence - does 'and' bind tighter than 'or', and where does 'not' sit?

← Operators and Expressions · Ref: Q2160

EK9 follows the convention of C, Java and Python. Tightest first: the postfix operators and the prefixes $ $$ #^ #< #> #? length abs sqrt ~ and 'is empty'; then '^' (right-associative, so 2 ^ 3 ^ 2 is 512); then unary minus (so -x ^ 2 is -(x ^ 2), and -2 ^ 2 is -(2 ^ 2) = -4.0 too - write (-2) ^ 2 to square the negative number); then the one coalescing level ?: ?? <? <=? >? >=?; then * / mod rem; + -; << >>; <=> <~>; then < <= > >= matches contains in and 'in a ... b'; then == <> !=; then word 'not'; then 'and'; then 'xor'; then 'or'; and the ternary last. So 'a or b and c' is 'a or (b and c)', 'name in names == expected' compares the membership, 'valid and age in 18 ... 65' is 'valid and (age in 18 ... 65)', and 'not age == limit' and 'not name in names' negate the whole test, as in Python. '~' applies to its operand alone, so '~name in names' asks a String for '~' (E07620). Word 'not' is never an operand of another operator unless parenthesised, except 'and', 'xor' and 'or' (E08273): read from the right it would NOT reach past an operator before it, so 'ready == not done' is a compile error, as in Python - write 'ready == (not done)', or 'not (ready == done)' - and every prefix operator binds tighter than it, so '$ not done' is an error too - write '$ (not done)'; word 'not', '~' and unary '-' are the same negation, so a negation applied to a negation cancels out and is E08274, in any mix of spellings and parenthesised or not ('not not done', 'not (not done)', '~~done', '~not done', '- -x', '-(-x)') - write 'done', or a single 'not done' (subtraction is not a negation: '-(a - b)' and 'a - -b' are fine); 'ready and not done' needs no parentheses because 'and', 'xor' and 'or' bind below 'not'. A coalescing operator ('??' included) mixed with any other binary operator needs parentheses (E08269): '(first ?? second) + third', never 'first ?? second + third'.

Example

defines module qa.operators.precedence

  defines constant
    low <- 18
    high <- 65

  defines function

    flagOf()
      -> answer as Boolean
      <- rtn as Boolean: answer

    numberOf()
      -> amount as Integer
      <- rtn as Integer: amount

  defines program

    OperatorPrecedenceDemo()
      stdout <- Stdout()
      yes <- flagOf(true)
      no <- flagOf(false)

      //'and' binds tighter than 'or': this is 'yes or (no and no)'.
      either <- yes or no and no
      stdout.println(`yes or no and no: ${either}`)

      //'^' associates right: 2 ^ (3 ^ 2).
      two <- numberOf(2)
      three <- numberOf(3)
      power <- two ^ three ^ two
      stdout.println(`2 ^ 3 ^ 2: ${power}`)

      //Unary minus binds below '^': -(x ^ 2).
      negated <- -three ^ two
      stdout.println(`-x ^ 2 with x = 3: ${negated}`)

      //The range test is a comparison: valid and (age in low ... high).
      age <- numberOf(30)
      inRange <- yes and age in low ... high
      stdout.println(`valid and age in low ... high: ${inRange}`)

      //'in' binds tighter than '==': (name in names) == expected.
      names <- ["Steve", "Stephen"]
      name <- "Steve"
      member <- name in names == yes
      stdout.println(`name in names == expected: ${member}`)

      //Word 'not' binds below the comparisons: not (age == low), not (name in names).
      notLow <- not age == low
      stdout.println(`not age == low: ${notLow}`)
      absent <- not name in names
      stdout.println(`not name in names: ${absent}`)

      //A negative literal is no exception to unary minus binding below '^': -(2 ^ 2).
      literal <- -2 ^ 2
      stdout.println(`-2 ^ 2: ${literal}`)

      //A coalescing operator mixed with another operator is parenthesised (E08269).
      total <- (three ?? two) + two
      stdout.println(`(x ?? y) + y: ${total}`)

      //Word 'not' as an operand of '==' is parenthesised (E08273).
      same <- yes == (not no)
      stdout.println(`yes == (not no): ${same}`)

      //Word 'not' as the operand of a prefix operator is parenthesised too (E08273).
      text <- $ (not no)
      stdout.println(`string of (not no): ${text}`)

      //A negation of a negation, parenthesised or not, cancels out: write the value itself (E08274).
      plain <- no
      stdout.println(`no without a double negation: ${plain}`)

Common mistakes

E07620 — '~' applies to 'name' alone and a String has no '~' operator. Word 'not' binds below the comparisons, so 'not name in names' negates the membership test. See ek9 -h E07620 for details.

Incorrect:

      absent <- ~name in names

Correct:

      absent <- not name in names

E08269 — '??' is on the one coalescing level, which binds tighter than '+', so the unparenthesised form would be '(three ?? two) + two' - and other languages read it the other way. Mixing a coalescing operator with another binary operator needs parentheses. See ek9 -h E08269 for details.

Incorrect:

      total <- three ?? two + two

Correct:

      total <- (three ?? two) + two

E08273 — Word 'not' binds below the comparisons, so on the right of '==' it would negate only 'no' while on the left it negates the whole comparison. Parenthesise the negation, or write 'not (yes == no)'. See ek9 -h E08273 for details.

Incorrect:

      same <- yes == not no

Correct:

      same <- yes == (not no)

E08273 — Word 'not' is never an operand of another operator unless parenthesised, except 'and', 'xor' and 'or'. '$' binds tighter than word 'not', so '$ not no' would convert only the negation - the same trap as 'yes == not no' written the other way round. Parenthesise the negation: '$ (not no)'. See ek9 -h E08273 for details.

Incorrect:

      text <- $ not no

Correct:

      text <- $ (not no)

E08274 — Word 'not', '~' and unary '-' are the same negation, so two of them cancel out and 'not not no' is just 'no'. EK9 cannot know whether the value or a single negation was meant, so it does not choose: write 'no', or a single 'not no'. See ek9 -h E08274 for details.

Incorrect:

      plain <- not not no

Correct:

      plain <- no

E08274 — Parentheses group, they do not change what is negated: 'not (not no)' is still a negation of a negation, and still just 'no'. Write the value, or a single 'not no'. See ek9 -h E08274 for details.

Incorrect:

      plain <- not (not no)

Correct:

      plain <- no
Other ways to ask this
  • Is 'a or b and c' read as 'a or (b and c)' in EK9?
  • Does 'not x == y' negate the comparison or x?
  • Is '^' right-associative in EK9?
  • What does '-x ^ 2' evaluate to in EK9?
  • Operator precedence table for EK9
  • Why is 'a == not b' a compile error in EK9?
  • Is '-2 ^ 2' equal to 4 or -4 in EK9?
  • Why is '$ not b' a compile error in EK9?

Coming from another language?

C/Java: '&&' over '||', '!' binds to its operand - EK9's 'not' binds below the comparisons like Python's 'not' and SQL's NOT, and '~' plays the part of '!'. Python: identical for and/or/not and for '**' (right-associative, above unary minus). Java has no power operator; Math.pow(-x, 2) is what EK9 writes as (-x) ^ 2.

Keywords: exponent, logical and, operator order, associativity, xor, logical not, logical or, precedence