Operators & precedence

Falx's parser builds expressions in a fixed precedence chain, from loosest-binding to tightest. This table lists it in that order - earlier rows bind more loosely.

LevelOperatorsAssociativity
1. Assignment= += -= *= /= %= ++ --Right
2. Logical OR||Left
3. Logical AND&&Left
4. Equality== !=Left
5. Comparison< <= > >=Left
6. Addition+ -Left
7. Multiplication* / %Left
8. Unary! - (negation)Right
9. Call / member / index() . []Left
output(2 + 3 * 4);   // 14 - multiplication binds tighter
output((2 + 3) * 4); // 20 - parens override precedence
There is no exponent operator, despite the lexer recognising **. The token exists internally, but the parser never wires it into an expression - writing 2 ** 3 is a syntax error today. For now, use repeated multiplication.

Arithmetic (+ - * / %)

Numbers behave as you'd expect, and an integer stays an integer unless a float is involved:

output(7 / 2);   // 3 - integer Ă· integer truncates towards zero
output(7.0 / 2); // 3.5 - a float on either side promotes the result
output(10 % 3);  // 1

+ is overloaded for strings and mixed types too - this is how string building works throughout the examples in this guide:

output("count: " + 5);    // "count: 5" - number is stringified
output(3 * "ab");         // "ababab" - number * string repeats it

Comparison (< <= > >=)

Ordering comparisons only work on numbers. Only FalxNumber implements a comparison; comparing two strings with < or > raises a runtime error rather than doing lexicographic comparison. If you need to order strings, convert or compare manually (e.g. via .length, or a custom function).
output(3 > 2);      // true
output("a" < "b");  // runtime error: Cannot compare 'string' with 'b'

Equality (== !=)

Unlike ordering, equality is defined for every type and never errors - it just compares by value, and different types are simply unequal instead of raising:

output(1 == "1");        // false - different types, not an error
output(1 == 1.0);        // true - numeric value, not representation, is compared
output([1,2] == [1,2]); // true - arrays/maps compare by contents

Logical (&& ||) and !

Both && and || short-circuit (the right side is only evaluated if needed), and both call the value's own boolean conversion directly rather than going through if's stricter rule - see Truthiness for exactly which types that works on before you reach for these on a non-boolean.

Assignment & compound assignment

= works on a plain variable, a struct field (obj.field = ...), an index (arr[0] = ...), or a tuple pattern (see Tuples & destructuring). The compound forms desugar to the equivalent binary operation:

let x = 10;
x += 5;  // same as: x = x + 5;
x -= 3;  x *= 2;  x /= 4;  x %= 4;
output(x); // 2

++ and -- are postfix only - x++ works, ++x is a syntax error. Both desugar the same way as the compound operators (x++ is x = x + 1), so there's no distinction between "pre" and "post" increment the way there is in C - the expression itself doesn't yield the old value, it's purely a statement-level shorthand.