Appendix D — Built-in Functions

AZM has four built-in functions you can use in any expression: sizeof, offset, LSB and MSB. The assembler evaluates all four entirely at assemble time — the Z80 sees only the resulting integer, never the function call. sizeof and offset work with the layout type system; LSB and MSB extract byte lanes from 16-bit values.


sizeof(TypeExpr)

Syntax:

sizeof(TypeName)
sizeof(TypeName[n])

sizeof returns the exact packed byte count for a layout type. For a record, that is the sum of its field sizes. For a scalar type, it returns the built-in size. For an array form, it multiplies the element size by the count.

sizeof(byte)         ; 1
sizeof(word)         ; 2

; Given: Sprite .type (x .field byte, y .field byte, flags .field byte, ptr .field word)
sizeof(Sprite)       ; 5
sizeof(Sprite[16])   ; 80
sizeof(byte[32])     ; 32

sizeof(TypeName) in an expression is equivalent to using the type name directly as a .ds size operand — both resolve to the same integer. The function form is useful when you need the constant outside a .ds context, or when the name alone would be ambiguous:

SPRITE_SIZE  .equ sizeof(Sprite)
TOTAL_BYTES  .equ MAX_COUNT * sizeof(Sprite) + sizeof(Header)

The full layout system, including how records and unions define their sizes, is covered in Chapter 5.


offset(TypeExpr, path)

Syntax:

offset(TypeName, fieldName)
offset(TypeName, outerField.innerField)
offset(TypeName, arrayField[n])
offset(TypeName, arrayField[n].innerField)
offset(TypeName[n], [index].fieldName)

offset returns the byte distance from the start of a type to the named field. For simple fields the path is just the field name. For a field that is itself a record, the path is dot-separated. For an array field, the path includes a bracket index.

Sprite  .type
x       .field byte      ; offset 0
y       .field byte      ; offset 1
flags   .field byte      ; offset 2
ptr     .field word      ; offset 3
        .endtype

offset(Sprite, x)       ; 0
offset(Sprite, y)       ; 1
offset(Sprite, flags)   ; 2
offset(Sprite, ptr)     ; 3

offset(Sprite[16], [3].flags)   ; offset of flags field in element 3

Dot paths reach through nested record fields:

Actor   .type
pos     .field Sprite    ; offsets 0–4
state   .field byte      ; offset 5
        .endtype

offset(Actor, pos.x)     ; 0
offset(Actor, pos.y)     ; 1
offset(Actor, state)     ; 5

Array indices in offset paths must be numeric literals. offset(Table, rows[0].x) is valid. Layout-cast path expressions (Chapter 5) accept assembler-time constant expressions in index positions.

Chapter 5 covers the full layout system including field declarations, nested records, unions and cast-path syntax.


LSB(expr) and MSB(expr)

Syntax:

LSB(expression)
MSB(expression)

LSB and MSB are acronyms — Least Significant Byte and Most Significant Byte — and are written in uppercase. The parser matches the exact tokens LSB and MSB.

LSB(expr) returns the low byte of the value:

LSB(value)  =  value & $FF

MSB(expr) returns the high byte:

MSB(value)  =  (value >> 8) & $FF

A common use is splitting a 16-bit address or constant into its two bytes, either for loading into a register pair or for embedding in a data table:

TARGET  .equ $C432

        ld   a, MSB(TARGET)    ; A = $C4
        ld   h, a
        ld   a, LSB(TARGET)    ; A = $32
        ld   l, a
        ; HL now holds $C432

; Stored as a little-endian address pair in a jump table:
JumpTable:
        .db LSB(routine_a), MSB(routine_a)
        .db LSB(routine_b), MSB(routine_b)
        .db LSB(routine_c), MSB(routine_c)

For source ported from assemblers that used LOW() or HIGH(), replace those calls with LSB and MSB.


Case sensitivity

All four functions are case-sensitive. The parser matches the exact tokens sizeof, offset, LSB and MSB. SIZEOF, Sizeof, Offset, lsb and msb are parse errors.