Casa uses postfix (reverse Polish) notation. Operands are pushed onto the stack first, then the operator consumes them and pushes the result. There is no operator precedence.
In most languages you write (3 + 4) * 2. In Casa, you write 3 4 + 2 *. Here is how it evaluates step by step:
Step Operation Stack (top on right)
──── ───────── ────────────────────
start [ ]
3 push 3 [ 3 ]
4 push 4 [ 3, 4 ]
+ add [ 7 ]
2 push 2 [ 7, 2 ]
* multiply [ 14 ]
The stack replaces parentheses and precedence rules. Values are consumed left to right, and every operator immediately uses the top values on the stack.
Arithmetic and comparison operators use different stack conventions:
- Arithmetic (
+ - * / % << >> & | ^):a b op=a op b. The value pushed first is the left operand. This means10 3 -is10 - 3 = 7— natural left-to-right reading. - Comparison (
== != < <= > >=):a b op=b op a. The top of the stack is the left operand, matching function call convention. This means0 1 >is1 > 0 = true.
10 3 - # 10 - 3 = 7 (arithmetic: left-to-right)
10 3 > # 3 > 10 = false (comparison: top is left operand)
All arithmetic operators consume two values and produce one.
| Operator | Stack Effect | Description |
|---|---|---|
+ |
a int -> a |
Addition. The second operand must be int; result type matches the first operand. Works on ptr for pointer arithmetic. |
- |
a int -> a |
Subtraction. Same typing rules as +. |
* |
int int -> int |
Multiplication |
/ |
int int -> int |
Integer division (truncates toward zero) |
% |
int int -> int |
Modulo (remainder) |
34 35 + print # 69
1357 20 - print # 1337
7 6 * print # 42
14 3 / print # 4
14 3 % print # 2
+ and - support ptr as the first operand, allowing offset-based heap access:
32 alloc = buf
42 buf (ptr) 8 + store64 # store 42 at byte offset 8
buf (ptr) 8 + load64 print # 42
| Operator | Stack Effect | Description |
|---|---|---|
<< |
a int -> a |
Left shift. Result type matches the first operand. |
>> |
a int -> a |
Right shift. Result type matches the first operand. |
1 4 << print # 16
16 4 >> print # 1
| Operator | Stack Effect | Description |
|---|---|---|
& |
int int -> int |
Bitwise AND |
| |
int int -> int |
Bitwise OR |
^ |
int int -> int |
Bitwise XOR |
~ |
int -> int |
Bitwise NOT (one's complement) |
12 10 & print # 8 (1100 AND 1010 = 1000)
12 10 | print # 14 (1100 OR 1010 = 1110)
12 10 ^ print # 6 (1100 XOR 1010 = 0110)
12 ~ print # -13 (inverts all bits)
Note:
&is also used as a function reference prefix (&name). When followed by an identifier, it creates a function reference. When used after two values on the stack, it performs bitwise AND.
All comparison operators consume two values of the same type and push a bool. Equality operators require the operand type to satisfy the Eq trait; ordering operators require Ord.
| Operator | Stack Effect | Description |
|---|---|---|
== |
[T: Eq] T T -> bool |
Equal |
!= |
[T: Eq] T T -> bool |
Not equal |
< |
[T: Ord] T T -> bool |
Less than |
<= |
[T: Ord] T T -> bool |
Less than or equal |
> |
[T: Ord] T T -> bool |
Greater than |
>= |
[T: Ord] T T -> bool |
Greater than or equal |
Built-in primitives (int, bool, char, cstr, ptr) and enums get direct bytecode comparison. User-defined types must provide impl T { fn eq ... } (and fn lt ... for ordering); the operator then lowers to the corresponding trait method call. See traits.md for Eq and Ord.
1 1 == print # true
1 0 != print # true
0 1 > print # true (1 > 0, top is left operand)
1 0 > print # false (0 > 1)
Comparison operators use the top of the stack as the left operand (a b op = b op a). See Operand order above.
String == and != compare by content (byte-by-byte), not by pointer identity. Other comparison operators (<, <=, >, >=) are not supported for strings.
"hello" "hello" == print # true
"hello" "world" != print # true
| Operator | Stack Effect | Description |
|---|---|---|
&& |
bool bool -> bool |
Logical AND |
|| |
bool bool -> bool |
Logical OR |
! |
bool -> bool |
Logical NOT |
true true && print # true
true false || print # true
true ! print # false
Assignment operators pop a value from the stack and store it in a named variable.
| Operator | Stack Effect | Description |
|---|---|---|
= name |
T -> None |
Assign top of stack to variable name |
= name:type |
T -> None |
Assign with type annotation, verifies and narrows the type |
+= name |
int -> None |
Add top of stack to variable name |
-= name |
int -> None |
Subtract top of stack from variable name |
42 = count # count is now 42
1 += count # count is now 43
10 -= count # count is now 33
The = name:type form lets you annotate the type of a variable at assignment time. The type checker verifies the stack value is compatible and uses the annotated type for the variable.
42 = x:int # explicit int annotation
Option::None = empty:Option[int] # narrow bare Option to Option[int]
Variables are created on first assignment. See Functions and Lambdas -- Variables for scoping rules.
- Types and Literals -- primitive types and type casting
- Functions and Lambdas -- variables and scoping rules
- Control Flow -- using conditions in
ifandwhile