Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

1.3 Expressions

Operator Precedence

udewy parses expressions using the precedence table below. Binary operators are left-associative within a precedence level, and parentheses can be used to override the default grouping.

Operand evaluation remains left-to-right, even when precedence groups the expression differently. For example, a + b * c evaluates a, then b, then c, and groups as a + (b * c).

Precedence levels (highest to lowest):

LevelOperatorsDescription
7*, //, %Multiplicative
6+, -Additive
5<<, >>Shift
4=?, not=?, >?, <?, >=?, <=?Comparison
3andBitwise/logical AND
2xorBitwise/logical XOR
1orBitwise/logical OR

Examples:

# OK: multiplicative operators bind before additive operators
let product_sum:int = a + b * c

# OK: explicit grouping
let grouped:int = (a + b) * c

# OK: left-associative within the same precedence level
let chain:int = a + b + c

Unary Operators

OperatorDescriptionSemantics
-NegationTwo's complement negation: 0 - x
notBitwise/logical NOTInverts all 64 bits

Unary operators bind tightly to their operand:

let x:int = -(a + b)
let y:int = not flags

Arithmetic Operators

OperatorDescriptionSemantics
+Addition64-bit wrapping addition
-Subtraction64-bit wrapping subtraction
*Multiplication64-bit wrapping multiplication
//Integer divisionSigned 64-bit division, truncated toward zero
%ModuloSigned 64-bit remainder

Division and modulo use signed interpretation (RISC-V div / rem semantics on all targets):

Casea // ba % b
b = 0-1 (0xFFFF_FFFF_FFFF_FFFF)a
INT_MIN // -1INT_MIN0

All other cases use truncating signed division toward zero.

Shift Operators

OperatorDescriptionSemantics
<<Left shiftShift left, fill with zeros
>>Right shift (unsigned)Logical shift right, fill with zeros

The shift amount is masked to the low 6 bits (0-63).

Important: The >> operator performs an unsigned (logical) shift, filling vacated bits with zeros regardless of the sign bit. For arithmetic (signed) right shift that preserves the sign bit, use the __signed_shr__ intrinsic. See Semantic Differences

Comparison Operators

All comparison operators return true (0xFFFF_FFFF_FFFF_FFFF) or false (0x0000_0000_0000_0000).

OperatorDescriptionSemantics
=?EqualTrue if operands are bit-identical
not=?Not equalTrue if operands differ in any bit
>?Greater thanSigned comparison
<?Less thanSigned comparison
>=?Greater or equalSigned comparison
<=?Less or equalSigned comparison

Note: Relational comparisons (>?, <?, etc.) interpret operands as signed 64-bit integers.

Bitwise/Logical Operators

OperatorDescriptionSemantics
andBitwise AND64-bit bitwise AND
orBitwise OR64-bit bitwise OR
xorBitwise XOR64-bit bitwise XOR

Due to the boolean representation (true = all 1s, false = all 0s), these operators work correctly as both bitwise and logical operators.

Important: In ordinary expressions, and and or are bitwise and both operands are always evaluated. In if and loop conditions only, and and or use logical short-circuit evaluation (compatible with Dewy bool conditions): the right-hand side is skipped when the result is already determined.

Parentheses and Grouping

Parentheses override precedence and grouping:

let x:int = (a + b) * c

Function Calls

Named call:

result = add(1 2)

Expression call (calling a computed function pointer):

let fn_ptr:int = get_handler()
(@fn_ptr)(arg1 arg2)

Arguments are space-separated (no commas).

NOTE: Indirect calls require parentheses around the callee. Bare fn_ptr(arg1 arg2) is always a named call: it looks up a top-level function fn_ptr and ignores any local or global binding of that name. Use (@fn_ptr)(arg1 arg2) to call through a value. Consequently, binding a local or global with the same name as an existing top-level function is ill-formed — name(...) will still call the function, not the binding.

@name is decorative in udewy: it is the value of name, exactly as the bare name is. It exists so a udewy program reads the same under Dewy, where a bare function name is always a call and @name means the function itself. Write it wherever a function is used as a value — return @double, let handler:int = @on_start — and it is required when a name in parentheses is called: (fn_ptr)(args) is rejected, because under Dewy that would call fn_ptr() first and apply the arguments to its result.