Skip to content

Operators

xtc operators are largely a subset of C’s, with two notable additions: a pair of rotate operators (<: and :>) that map directly to the 6502’s ROL / ROR instructions, and four byte-extract prefix operators (<, >, >>, >>>) that pull individual bytes out of wider constants and symbols. The byte-extract operators only mean anything inside asm { ... } blocks.

Listed from highest to lowest. Rows at the same level have the same precedence; within a level, Assoc records the associativity.

OperatorsAssocNotes
a[i], f(...), ., ->, postfix ++, postfix --leftprimary
prefix + -, !, ~, prefix ++, prefix --, @ (deref), & (addr-of), (type) cast, sizeof(), < > >> >>> (byte extract, asm)rightunary
*, /, %leftmultiplicative
+, -leftadditive
<:, :>leftrotate (ROL / ROR)
<<, >>leftshift (ASL / ASR)
<, >, <=, =>leftrelational
==, !=leftequality
&leftbitwise AND
^leftbitwise XOR
|leftbitwise OR
&&leftlogical AND
||leftlogical OR
? :rightternary
=, +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, <:=, :>=rightassignment

<: and :> are rotate through carry — they map to the 6502 ROL / ROR instructions, which read and write the carry flag. << and >> are arithmetic shift (ASL / ASR for the right shift). Use rotate when you want to chain bytes through carry; use shift when you want a regular multiply / divide by powers of two.

u8 a = $80;
u8 b = a <: 1; // a is rotated left through carry; one-bit shift left + carry-in
u8 c = a << 1; // arithmetic left shift; bit 7 lost

@ is the unary dereference operator; & takes an address. They’re inverses:

u8 v = 42;
u8@ p = &v;
u8 q = @p; // q == 42
@p = 99; // v becomes 99

-> is sugar for “dereference then access a member”: p->x is exactly (@p).x. xtc also accepts the dot form p.x directly on a pointer — the compiler auto-dereferences. See Types → Pointers for the rationale.

Inside the (type) cast, two forms have non-C semantics:

  • (Dog@) animal — runtime-checked downcast. Traps on mismatch.
  • (Dog@ ?) animal — failable downcast. Yields (Dog@)0 on mismatch.

These are class-pointer-only; (u16 ?)x is a compile-time error. Details on Inheritance & protocols.

sizeof(T) evaluates to a compile-time u16 byte count. Works on any type, including struct and class.

These operators have meaning only inside an asm { ... } block, where they let you reach individual bytes of a constant or symbol that the assembler would otherwise treat as a 16- or 32-bit address:

PrefixMeaning
<xlow 8 bits of x
>xbits 8..15 of x
>>xbits 16..23 of x
>>>xbits 24..31 of x
u16 val = $1234;
asm {
lda #<val; // LDA #$34
ldx #>val; // LDX #$12
}

Outside an asm block these tokens parse as their normal precedences — < and > as relational comparisons, >> as the shift operator. The byte-extract reading is unambiguous in context because the assembler-level grammar accepts only constants and symbols after the prefix.

Every binary operator that’s also an arithmetic, bitwise, or shift operation has a compound-assignment form: +=, -=, *=, /=, %=, &=, |=, ^=, <<=, >>=, plus the rotate forms <:= and :>=. They behave exactly as their expansion suggests:

x += 1; // x = x + 1
flags &= ~MASK; // flags = flags & ~MASK
acc <:= 1; // acc = acc <: 1

When the left-hand side goes through a property setter (see Classes → Properties), the desugaring evaluates the base expression twice — free for identifiers and self, watch-out only if the base has a side effect.