c65gm/syntax.md

11 KiB

Syntax Reference

Comments

C65GM uses C-style line comments.

Syntax:

// <comment text>

Examples:

BYTE counter = 0    // Initialize counter
// This is a full line comment
FOR i = 0 TO 10     // Loop through values
    counter++       // Increment
NEXT

Notes:

  • Comments start with // and continue to end of line
  • Comments in ASM blocks use assembly syntax: ;
  • Comments in SCRIPT blocks use Starlark syntax: #

Preprocessor Directives

#DEFINE

Defines a text substitution macro.

Syntax:

#DEFINE <n> [= <value>]

Examples:

#DEFINE MAX_SPEED = 10
#DEFINE HELLO = GOODBYE // Will replace the text HELLO in all source with GOODBYE (except ASM and SCRIPT blocks)
#DEFINE HELLO2 = GOOOOOODBYE // Longest match wins. If HELLO2 is encountered this will be the match not the HELLO define above 
#DEFINE SCREEN = $$0400 // Notice $$, see below.
#DEFINE OFFSET = 255

Note: Replacements are not recursive. Once a match is found, it's replaced, and processing starts after the replacement.

Special Characters in Values:

Define values support $ escape sequences for special characters:

  • $XX (two hex digits) = character with code XX
  • $$ = literal $ character
#DEFINE SPACE = $20          // space character
#DEFINE NEWLINE = $0D        // carriage return
#DEFINE TAB = $09            // tab character
#DEFINE HEXADDR = $$D020     // stores "$D020" as text

Notes:

  • Optional = separator
  • Value can contain previously defined macros
  • Macros are expanded in source lines
  • Case sensitive
  • $ escapes are processed when the define is created, not when used
  • For most purposes, use WORD CONST or BYTE CONST instead of preprocessor defines to define constant named values

#UNDEF

Undefines a previously defined macro.

Syntax:

#UNDEF <n>

Examples:

#DEFINE DEBUG = 1
#UNDEF DEBUG

#IFDEF

Conditional compilation if macro is defined.

Syntax:

#IFDEF <n>

Examples:

#IFDEF DEBUG
    BYTE debugFlag = 1
#IFEND

#IFDEF PAL
    BYTE scanlines = 312
#IFEND

#IFNDEF

Conditional compilation if macro is not defined.

Syntax:

#IFNDEF <n>

Examples:

#IFNDEF __INCLUDED_SPRITE_LIB
    #DEFINE __INCLUDED_SPRITE_LIB = 1
    FUNC drawSprite
        ; sprite code
    FEND
#IFEND

Notes:

  • Common pattern for include guards
  • Prevents multiple inclusion

#IFEND

Ends conditional compilation block.

Syntax:

#IFEND

Examples:

See #IFDEF and #IFNDEF

#INCLUDE

Includes another source file.

Syntax:

#INCLUDE <filename>
#INCLUDE <libfile>

Examples:

#INCLUDE sprites.c65
#INCLUDE lib/math.c65
#INCLUDE "constants.c65"
#INCLUDE <stdlib.c65>

Notes:

  • Relative path: resolves relative to current file
  • Angle brackets <file>: searches in library path defined by C65LIBPATH environment variable
  • Supports nested includes
  • Use with #IFNDEF for include guards

#PRINT

Prints message during compilation.

Syntax:

#PRINT <message>

Examples:

#PRINT Compiling main module
#PRINT MAX_VALUE
#PRINT Debug build enabled

Notes:

  • Useful for debugging preprocessor
  • Macros expanded before printing

#HALT

Stops compilation immediately.

Syntax:

#HALT

Examples:

#IFDEF INCOMPLETE
    #PRINT Feature not implemented
    #HALT
#IFEND

Notes:

  • Halts entire compilation process, not just preprocessing
  • Returns exit code 2

#PRAGMA

Sets compiler pragmas (options).

Syntax:

#PRAGMA <n> [<value>]

Available Pragmas:

_P_USE_LONG_JUMP

  • Uses JMP instead of branch instructions for IF/WHILE/FOR
  • Needed when branch targets exceed 127 byte range
  • Value: any non-zero value enables

_P_USE_IMMUTABLE_CODE

  • Prevents self-modifying code generation
  • Required for ROM-based code
  • Errors on PEEK/POKE/GOSUB with variable addresses

_P_USE_CBM_STRINGS

  • Encodes strings in CBM PETSCII format
  • Default: ASCII encoding
  • Value: any non-empty value enables

Examples:

#PRAGMA _P_USE_LONG_JUMP 1
#PRAGMA _P_USE_IMMUTABLE_CODE 1
#PRAGMA _P_USE_IMMUTABLE_CODE 0 //turn off pragma
#PRAGMA _P_USE_CBM_STRINGS 1

Code Blocks

ASM...ENDASM

Inline assembly code block.

Syntax:

ASM
    <assembly code>
ENDASM

Examples:

ASM
    lda #$00
    sta $d020
    jsr $ffd2
ENDASM

BYTE temp = 5
ASM
    lda temp
    clc
    adc #10
    sta temp
ENDASM

FUNC calculate(value)
    BYTE local = 10
    ASM
        lda |local|        ; Reference local variable
        clc
        adc value
        sta |local|
    ENDASM
FEND

Notes:

  • Assembly code passed through to ACME assembler unchanged
  • Can reference global variables by name
  • Local variables inside FUNC must use |varname| syntax
  • Comments in ASM blocks use ; (assembly syntax)
  • No macro expansion inside ASM blocks

SCRIPT...ENDSCRIPT

Starlark script code block for generating assembly code.

Scripts are written in Starlark (Python-like language) and executed at compile time. Output from print() statements is fed directly into the assembler.

Syntax:

SCRIPT
    <starlark code>
ENDSCRIPT

Examples:

Simple data generation:

SCRIPT
    for i in range(256):
        print("    !8 %d" % i)
ENDSCRIPT

Sine table generation:

SCRIPT
    import math
    print("sintable:")
    for i in range(256):
        angle = (i * 2.0 * math.pi) / 256.0
        sine = math.sin(angle)
        value = int((sine + 1.0) * 127.5)
        print("    !8 %d" % value)
ENDSCRIPT

Using compiler variables:

BYTE tableSize = 64

SCRIPT
    # Reference variables in generated assembly
    print("lookup:")
    for i in range(64):
        print("    !8 %d" % (i * 2))
    print("    ; Table size is stored in |tableSize|")
ENDSCRIPT

Notes:

  • Scripts use Starlark language (Python-like subset)
  • Comments in SCRIPT blocks use # (Python syntax)
  • Output from print() goes directly to assembler
  • Can reference compiler variables using |varname| syntax
  • Math module available: import math
  • Maximum 1 million execution steps (prevents infinite loops)
  • Executed at compile time, not runtime

SCRIPT LIBRARY...ENDSCRIPT

Defines reusable Starlark functions that persist across all subsequent SCRIPT blocks.

Syntax:

SCRIPT LIBRARY
    <starlark function definitions>
ENDSCRIPT

Examples:

Defining reusable functions:

SCRIPT LIBRARY
def emit_nops(count):
  for i in range(count):
    print("  nop")

def emit_delay(cycles):
  for i in range(cycles // 4):
    print("  nop")
    print("  nop")
  remainder = cycles % 4
  if remainder >= 3:
    print("  bit $ea")
    remainder -= 3
  for i in range(remainder // 2):
    print("  nop")
ENDSCRIPT

Using library functions in SCRIPT blocks:

SCRIPT
  emit_nops(5)
  emit_delay(10)
ENDSCRIPT

Notes:

  • Functions defined in SCRIPT LIBRARY are available to all subsequent SCRIPT, SCRIPT LIBRARY, and SCRIPT MACRO blocks
  • Multiple SCRIPT LIBRARY blocks accumulate - later blocks can use functions from earlier ones
  • Typically placed in include files for reuse across projects
  • The library block itself can also use print() to emit code, but this is uncommon

SCRIPT MACRO...ENDSCRIPT

Defines a named, parameterized macro that expands inline when invoked.

Syntax:

SCRIPT MACRO name(param1, param2, ...)
    <starlark code>
ENDSCRIPT

Invocation:

@name(arg1, arg2, ...)           // Outside ASM blocks
|@name(arg1, arg2, ...)|         // Inside ASM blocks

Examples:

Defining macros:

SCRIPT MACRO delay(cycles)
  if cycles < 2:
    fail("Cannot delay less than 2 cycles")
  emit_delay(cycles)
ENDSCRIPT

SCRIPT MACRO set_irq(handler)
  print("  lda #<%s" % handler)
  print("  sta $fffe")
  print("  lda #>%s" % handler)
  print("  sta $ffff")
ENDSCRIPT

Invoking macros outside ASM blocks:

BYTE CONST CYCLES_PER_LINE = 63

@delay(10)
@delay(CYCLES_PER_LINE-20)
@set_irq(my_handler)

Invoking macros inside ASM blocks:

ASM
  lda #$00
  |@delay(8)|
  sta $d020
ENDASM

Parameter Types:

  1. Integer expressions - Evaluated at compile time:

    • Literal numbers: 10, $FF, %11110000
    • Constants: CYCLES_PER_LINE, MAX_VALUE
    • Arithmetic: CYCLES_PER_LINE-20, BASE+OFFSET
    • Passed to macro as Starlark int
  2. Labels/Identifiers - Passed as strings:

    • Bare identifiers that don't resolve to constants: my_handler, loop_start
    • Passed to macro as Starlark string
    • Use in generated assembly: print(" jmp %s" % label)

Checking parameter types in macros:

SCRIPT MACRO flexible(value)
  if type(value) == "int":
    print("  lda #%d" % value)
  else:
    print("  lda %s" % value)
ENDSCRIPT

Error handling with fail():

SCRIPT MACRO delay(cycles)
  if cycles < 2:
    fail("Cannot delay less than 2 cycles")
  emit_delay(cycles)
ENDSCRIPT

The fail() function stops compilation with an error message pointing to the macro invocation site.

Notes:

  • Macros are NOT executed at definition time - only when invoked
  • Macros have access to all SCRIPT LIBRARY functions
  • print() output replaces the macro invocation site
  • Everything expands inline - no JSR/RTS overhead
  • Use for cycle-critical code like raster timing

Expression Syntax

Expressions evaluate left to right with no operator precedence.

Number Formats

Decimal:

123
0
255
65535

Hexadecimal ($-prefix):

$FF
$D020
$C000
$00

Binary (%-prefix):

%11111111
%10000000
%00000001
%11110000

Operators

Evaluated strictly left to right:

  • + Addition
  • - Subtraction
  • * Multiplication
  • / Division
  • | Bitwise OR
  • & Bitwise AND
  • ^ Bitwise XOR

Constants

Named constants defined with BYTE CONST or WORD CONST:

BYTE CONST MAX_SPEED = 10
WORD CONST SCREEN = $0400

speed = MAX_SPEED
pointer = SCREEN

Expression Examples

value = 100+50
result = $FF-10
address = $D000+32
mask = %11110000&$0F
combined = base|offset
calculated = start+length*2

Critical: No operator precedence. Evaluation is strictly left to right:

result = 2+3*4    ; evaluates as (2+3)*4 = 20, not 2+(3*4) = 14
value = $10|$20&$0F   ; evaluates as ($10|$20)&$0F, not $10|($20&$0F)

Usage Contexts

Expressions accepted anywhere a numeric literal is expected:

Variable initialization:

BYTE count = 10+5
WORD addr = $C000+$100

Absolute addresses:

BYTE screen @ $0400+40*10
INC $D000+20

Command parameters:

FOR i = 0 TO MAX_VALUE-1
IF x > THRESHOLD+10
POKE $D020+offset WITH value
result = PEEK $0400+index

Arithmetic operations:

sum = value1+value2
product = base*factor
adjusted = original+OFFSET

Limitations

No parentheses for grouping:

; NOT SUPPORTED:
result = (a+b)*c
value = base+(offset*2)

No nested expressions in assignments:

; NOT SUPPORTED:
x = y + z    ; only single value or constant expression
x = a+b      ; constant expression (no spaces) OK if a,b are constants

Workaround for complex expressions: Use temporary variables:

temp = a + b
result = temp - c

Constant Expressions

Expressions without spaces are treated as constant expressions:

value = 100+50       ; OK - constant expression
value = 100 + 50     ; ERROR - not a simple assignment
value = MAX+10       ; OK if MAX is constant