# Syntax Reference

## Comments

C65GM uses C-style line comments.

**Syntax:**
```
// <comment text>
```

**Examples:**
```
BYTE counter = 0    // Initialize counter
BYTE i              // Loop variable (must be declared)
// 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
- Value: any non-zero value enables

**_P_USE_CBM_STRINGS**
- Encodes strings in CBM PETSCII format
- Default: ASCII encoding
- Value: any non-zero value enables

**_P_IGNORE_UNUSED**
- Suppresses "variable declared but never used" warnings
- Useful for scratch space variables or legacy code
- Value: "0" enables warnings, any non-"0" value disables warnings
- Note: Do not use `=` sign, use space: `#PRAGMA _P_IGNORE_UNUSED 1`

**_P_REMOVE_UNUSED**
- When enabled (value ≠ "" and ≠ "0"), unused functions with this pragma will be removed from the final assembly output
- Requires function to be marked with `#PRAGMA _P_REMOVE_UNUSED 1` at function scope
- Functions are only removed if they are never called in the program
- The compiler will output an info message to stdout when a function is removed

**_P_ASM_AFTER_VARS**
- When enabled (value ≠ "" and ≠ "0"), ASM blocks are placed after all variable storage and constant strings
- Useful for embedding binary data (graphics, music) with labels after the variable area
- The compiler adds a comment `; ASM block deferred to end of source` at the original location
- The deferred block includes source location: `; ASM Block from <filename>, Line <line number>`
- Value: any non-zero 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
#PRAGMA _P_IGNORE_UNUSED 1      //suppress unused variable warnings
#PRAGMA _P_IGNORE_UNUSED 0      //enable unused variable warnings
#PRAGMA _P_REMOVE_UNUSED 1      //remove function if unused
#PRAGMA _P_REMOVE_UNUSED 0      //keep function even if unused (default)
#PRAGMA _P_ASM_AFTER_VARS 1     //place ASM blocks after variables
#PRAGMA _P_ASM_AFTER_VARS 0     //normal ASM block placement (default)
```

---

## 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({BYTE value})
    BYTE local = 10
    ASM
        lda |local|        ; Reference local variable
        clc
        adc value
        sta |local|
    ENDASM
FEND

#PRAGMA _P_ASM_AFTER_VARS 1
ASM
sprite_data: !binary "sprite.bin"
    !8 1,2,3,4
ENDASM
#PRAGMA _P_ASM_AFTER_VARS 0
```

**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 per block (prevents infinite loops). Each `SCRIPT`, `SCRIPT LIBRARY`, and `@macro()` invocation gets its own step counter. The limit is not cumulative across the compilation.
- If a block exceeds the limit, compilation fails with: `Starlark computation cancelled: too many steps`
- Use `#PRAGMA _P_SCRIPT_MAX_STEPS <n>` to change the limit (e.g., `#PRAGMA _P_SCRIPT_MAX_STEPS 5000000` for more steps, or `#PRAGMA _P_SCRIPT_MAX_STEPS 100` for a tight limit during debugging). The pragma is sticky — it applies to all subsequent blocks until changed.
- Executed at compile time, not runtime

**File I/O Built-in Functions:**

Scripts can read files from the project folder (the directory containing the main input .c65 file) using the following built-in functions:

- **`load_binary(path, offset=0, length=0)`** — Loads a binary file and returns a list of integers (0-255). Optional `offset` specifies a starting byte position, and `length` limits the number of bytes read (0 = read all remaining bytes).
- **`load_text(path)`** — Loads a text file and returns a list of strings, one per line. Handles both `\n` and `\r\n` line endings.

**Security restrictions:**
- Only relative paths are allowed (no absolute paths like `/etc/passwd`)
- Path traversal (`..`) is not permitted
- Only files inside the project folder (or its subdirectories) can be accessed
- File access errors cause a compile error with source location

**Example — loading sprite data:**

```
SCRIPT
    sprite = load_binary("assets/hero.spr")    # load binary data
    print("hero_sprite:")
    for i in range(0, len(sprite), 8):
        row = ", ".join(["$%02x" % b for b in sprite[i:i+8]])
        print("  !byte " + row)
ENDSCRIPT
```

**Example — loading a level map:**

```
SCRIPT LIBRARY
    # Load a text-based level map at compile time
    level = load_text("levels/lvl1.txt")
    height = len(level)
    width = len(level[0]) if height > 0 else 0

    def emit_map():
        print("level_map:")
        for y in range(height):
            print("  !text " + repr(level[y]))
        print("level_height:")
        print("  !8 %d" % height)
        print("level_width:")
        print("  !8 %d" % width)
ENDSCRIPT
```

---

### 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 (no precedence):

- `+` Addition (runtime and constant expressions)
- `-` Subtraction (runtime and constant expressions)
- `*` Multiplication (constant expressions only)
- `/` Division (constant expressions only)
- `|` Bitwise OR
- `&` Bitwise AND
- `^` Bitwise XOR
- `<<` Shift left
- `>>` Shift right

There are two distinct expression forms:

- **Constant expressions** — written *without spaces* (e.g. `2+3*4`). Every
  term must be a literal or a `CONST`; these are folded at compile time and
  may chain any number of terms. `*` and `/` are only available here.
- **Runtime expressions** — written *with spaces* (e.g. `a + b`). These
  perform exactly one operation, where at least one operand may be a variable.
  Only `+ - & | ^ << >>` are supported; `*` and `/` are not.

### 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

Constant expressions (no spaces, all terms literals or constants):
```
value = 100+50
result = $FF-10
address = $D000+32
mask = %11110000&$0F
combined = BASE|OFFSET
calculated = START+LENGTH*2
```

Runtime expressions (spaces, one operation, may use variables):
```
combined = base | offset
adjusted = value + count
```

**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, value
result = PEEK $0400+INDEX
```

**Arithmetic operations:**

Constant expressions (compile-time, all terms literal/CONST):
```
sum = VALUE1+VALUE2
adjusted = BASE+OFFSET
```

Runtime expressions (one operation, may use variables):
```
sum = value1 + value2
adjusted = original + offset
```

### Limitations

**No parentheses for grouping:**
```
; NOT SUPPORTED:
result = (a+b)*c
value = base+(offset*2)
```

**No multi-operation runtime expressions:**
```
; NOT SUPPORTED (more than one operation with variables):
x = a + b + c    ; chain of runtime operations
x = a + b * c    ; ditto

; SUPPORTED:
x = y + z        ; single runtime operation (one operator)
x = a+b          ; constant expression (no spaces) if a,b are literals/CONST
```

**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       ; constant expression (folded at compile time)
value = 100 + 50     ; single runtime operation (also OK)
value = MAX+10       ; constant expression, OK if MAX is a constant
```

The difference matters for chaining: `2+3*4` (no spaces) folds all terms at
compile time, while a spaced form allows only one operation.