Expand/Shrink

Precedence Chart

The precedence of operators in expressions is as follows:

      highest precedence:   parenthesis/function calls              (internal values)
subscripts/slices . (pSubsc = 11)
unary- unary+ not ~ (pUnary = 10)
* / (pMuldiv = 9)
+ - (pAddsub = 8)
>> << (pBitshift = 7)
& (pConcat = 6)
&& || (pBitops = 5)
< > <= >= (pRelops = 4)
= != (pEqorne = 3)
and or xor (pLogop = 2)
      lowest precedence:   { , , , } (pAllops = 0)

Thus 2+6*3 means 2+(6*3) rather than (2+6)*3. Operators on the same line have equal precedence and are evaluated left to right.

The use of explicit parenthesis when mixing anything other than the four main arithmetic ( * / + - ), unary ( + - not ~ ) and concatenation ( & ) operators is generally advised, except when doing so would be blatently gratuitous, overly tedious, and/or completely unnecessary.

The equals symbol ('=', or ':=') used in an assignment statement is not an operator, just part of the syntax of the language, whereas it is an operator (same as '==') when used mid-expression. You may prefer to use the ':=' and '==' forms to avoid any potential ambiguity, albeit such things are normally pretty self-evident from context anyway.
In many other languages '=' is an operator that returns the result of the assignment, allowing and encouraging the infamous and notorious if(a=b) slip-up, an otherwise legal but often mis-typed if(a==b), which has made many a programmer desperately resort to coding all their tests in an awkward and un-natural 'backward' style, eg if('Y'==ch), which obviously cannot possibly help one iota when comparing two variables anyway.
In contrast, the only way to achieve an assignment mid-expression in phix would be via a function call, which cannot modify local variables, and due to short-circuit evaluation may not even get called.
Hence that sort of thing is generally discouraged, that is when aiming for clear, intuitive, and readable code. I would also note that it is easier to debug integer tmp = <expr>; if tmp, plus (with a better name) it can be easier/faster to comprehend, while having no real additional overhead compared to the un-named temp that the compiler would otherwise quietly use anyway - the only overhead is that some symbol table entry will have say "padding" rather than -1 as a name, and any ex.err will/may have an extra potentially helpful line or two.

In practice, for instance, pLogop causes parsing of a and b and c to pause at the a and b stage, whereas (an outer) pAllops triggers left-association, ie (a and b) and c. Note that I agonised over setting the precedence levels of && || << and >>, eventually copying from Python/C/JavaScript only to find several expressions still required parenthesis, eg hi << 16 + lo is treated as hi << (16 + lo) rather than the (hi << 16) + lo that was needed and initially expected. Conversely, the (usually) equivalent hi << 16 || lo works the same as (hi << 16) || lo even without the parenthesis, and likewise lo || hi << 16 is the same as lo || (hi << 16).
Note that a && b != c is (a && b) != c as per Python, rather than a && (b != c) as per C/JavaScript.
Aside: watch out for || hitting 32-bit limits (ie truncating/masking inputs) that + does not.

The eagle-eyed may have spotted the leap from 0 to 2 over a missing 1. Originally it was intended for "and" to have a slightly different precedence than "or" (/"xor") but in the end I decided that forcing parenthesis was simply better. (Even with Python/C/JavaScript operator precedence tables to hand, I still can’t quite say which should be lower/higher. Don’t tell me. No, seriously, just don’t bother telling me, and if you try I will stick my fingers in my ears and start humming.)

Should you ever need to refer to this table to figure out what something means or should do, then for the love of Pete please add the appropriate paranthesis to the code in question. Needing too many parenthesis is a pretty sure sign that hoisting (common) subexpressions out into appropriately named temporary variables is the good and decent thing to do. Extra parenthesis will never make anything slower, except where doing the right thing and getting the right answer is slower than doing the wrong thing and getting the wrong answer.
Parenthesis is required to mix and, or, and xor in an expression. If the compiler finds "a or b and c" it will not assume "(a or b) and c" over "a or (b and c)" (or vice versa) but demands that you state exactly which you mean. Most programmers have at one point or another coded something apparently "obvious" such as
    if a and b
    or c and d then
or
    if  a or b 
    and c or d then
and then been surprised when (in some other programming language) it does not work. Other compilers may treat them as
    (a and b) or (c and d)  -- which is probably as intended
    (a or (b and c)) or d   -- somewhat less likely to be as intended
    ((a and b) or c) and d  -- even ""
    ((a or b) and c) or d   --  ""
To avoid such issues, phix simply forces the programmer to supply enough additional explicit parenthesis until everything is completely unambiguous, eg/ie
    if (a and b)
    or (c and d) then
or
    if  (a or b)
    and (c or d) then
The higher precedence of not means that (eg) not a or not b and (not a) or (not b) are equivalent, making the extra parenthesis strictly unnecessary, however I consider the latter good practice, plus be warned that phix is more like C-based languages, which treat (!a==b) as ((!a)==b) whereas in Python (for example) the precedence of the otherwise equivalent "not" is much lower, so not a == b is treated as not (a == b) - fwiw, I would agree that having not a == b behave exactly the same as a != b is not particularly helpful, so Phix don’t do dat. Mind you, it’s not quite as whacky as PHP and Ruby having && and and, likewise || and or, as two (pairs of two) operators with the same meaning but different precedences (wow!). [In Phix, & is the concatenation operator, there are no | or ^ or ** operators and no single-character alternatives to and/or/xor. The && is bitwise whereas "and" is logical, likewise || and "or".]

Technically the ellipsis operator (..) within a slice has the lowest possible precedence, so that eg s[i-1..i+1] is s[(i-1)..(i+1)] and not some nonsense like s[i-((1..i)+1)] which is of course meaningless and illegal.

On a related note, "a*(b/c)" and "(a*b)/c" are mathematically equivalent, however precision limits of the physical hardware may mean they give very different results, especially for partial results that approach or exceed the floating point hardware limits. In practice the compiler treats "a*b/c" as "(a*b)/c", however in general that is an implementation detail that should not be overly relied on - should one day some bright spark invent a safe and simple method to automatically minimise precision loss in the un-parenthesised case, I would take it. (I did once work with an actual Dr Watson, PhD in Nuclear Physics, no less, who spent many weeks on almost exactly such a routine, that I never quite managed to understand, but it’s all long gone now.)

Other programming languages may have subtle differences in precedence. For instance, in python 'or' has a lower precedence than 'and', so take that into account when translating some python code and Phix starts demanding extra parenthesis. In python the 'not' operator has a lower precedence than '+', so idx + not flag + offset is treated as idx + not (flag + offset), quite unlike the Phix interpretation which is idx + (not flag) + offset. My recommendation is to use as much parenthesis as you can bear, and when it gets too much that is as good an excuse as any to break the expression down into more manageable pieces. Besides, storing partial results in appropriately named variables can not only make the intent much clearer but also make debugging significantly easier and faster, and does not normally incur any additional penalty whatsoever over the hidden unnamed temporary variable that the compiler would otherwise use anyway (except as noted at the very end of the ternary ops technicalia drop-down).

Note that phix does not really do associativity handling: of course unary operators are right associative, because they have to be, but everything else is left associative. eg
    a*b/c*d/e is ((((a*b)/c)*d)/e)
    a*-b/c*not d/e is ((((a*(-b))/c)*(not d))/e)
    a == b == c is (a == b) == c    -- (unlike Python in which it is a == (b == c)...)
See also Perl/Raku operators