c65gm/language.md

984 lines
16 KiB
Markdown

# Programming in c65gm
c65gm is a high-level language for 6502 assembly programming that combines modern programming constructs with direct hardware access. Originally designed for Commodore 64 development, it compiles to efficient 6502 assembly code.
## Table of Contents
1. [Getting Started](#getting-started)
2. [Variables and Types](#variables-and-types)
3. [Expressions and Operators](#expressions-and-operators)
4. [Control Flow](#control-flow)
5. [Functions](#functions)
6. [Memory Operations](#memory-operations)
7. [Code Blocks](#code-blocks)
8. [Preprocessor](#preprocessor)
9. [Writing Libraries](#writing-libraries)
10. [Common Patterns](#common-patterns)
---
## Getting Started
### Your First C64 Program
Here's a simple complete program that changes the screen border color:
```c65
#INCLUDE <c64start.c65>
#INCLUDE <c64defs.c65>
GOTO start
FUNC main
BYTE borderColor @ $D020
borderColor = color_green
FEND
LABEL start
main()
```
### Creating a C64 Executable
To create a runnable C64 program, always start with:
```c65
#INCLUDE <c64start.c65>
```
This creates a valid C64 executable with a BASIC loader (`0 SYS 2064`) and sets up the proper memory layout.
### Program Structure
A typical c65gm program for C64 follows this pattern:
```c65
#INCLUDE <c64start.c65>
#INCLUDE <c64defs.c65> // Optional: standard C64 definitions
GOTO start // Jump over your function definitions
// Variables
BYTE counter = 0
WORD screen = $0400
// Functions - must be declared before we use them.
FUNC processData
// Do something
FEND
FUNC initialize
counter = 10
processData()
FEND
// Entry point - execution starts here
LABEL start
initialize()
```
**Important:** The `GOTO start` jumps over your function definitions. Without it, the CPU would try to execute directly into your function code, causing a crash. Always put `GOTO start` before your functions, and `LABEL start` before your actual program entry point.
---
## Variables and Types
### BYTE Variables
BYTE variables store 8-bit values (0-255).
```c65
BYTE count // Uninitialized
BYTE speed = 5 // Initialized to 5
BYTE screen @ $D020 // Memory-mapped to specific address
BYTE CONST MAX_SPEED = 10 // Constant (recommended over #DEFINE)
```
### WORD Variables
WORD variables store 16-bit values (0-65535).
```c65
WORD counter // Uninitialized
WORD address = $C000 // Initialized to hex value
WORD message = "Hello" // String literal (pointer to text)
WORD screenPtr @ $FB // Zero-page pointer
WORD CONST SCREEN_RAM = $0400 // Constant
```
### Memory-Mapped Variables
Variables can be placed at specific addresses using `@`:
```c65
BYTE borderColor @ $D020 // VIC-II border color
WORD irqVector @ $0314 // IRQ vector
WORD zpPointer @ $FB // Zero-page variable (fast access)
```
### Constants
Use `CONST` keyword for named constants (preferred over preprocessor defines):
```c65
BYTE CONST MAX_ITEMS = 100
WORD CONST SCREEN_START = $0400
WORD CONST CHAR_ROM = $D000
// Usage
BYTE itemCount = MAX_ITEMS
screen = SCREEN_START
```
---
## Expressions and Operators
### Number Formats
```c65
value = 123 // Decimal
value = $FF // Hexadecimal ($ prefix)
value = %11111111 // Binary (% prefix)
```
### Arithmetic Operators
```c65
result = 10 + 5 // Addition
result = 100 - 5 // Subtraction
```
### Bitwise Operators
```c65
mask = value & $FF // Bitwise AND
flags = flags | $01 // Bitwise OR
toggle = value ^ $FF // Bitwise XOR
```
### Increment and Decrement
```c65
counter++ // Increment by 1
counter-- // Decrement by 1
index++
lives--
```
### Critical: No Operator Precedence
**Expressions evaluate strictly left to right!** There is no operator precedence.
```c65
result = 2+3*4 // Evaluates as (2+3)*4 = 20, NOT 2+(3*4) = 14
value = 100-20+5 // Evaluates as (100-20)+5 = 85
```
For complex expressions, use temporary variables:
```c65
// Instead of: result = (b - c) + a
temp = b - c
result = a + temp
```
---
## Control Flow
### IF Statements
Basic conditional execution:
```c65
IF count == 10
result = 1
ENDIF
IF value > threshold
process()
ELSE
skip()
ENDIF
```
Supported comparison operators: `=` `==` `<>` `!=` `>` `<` `>=` `<=`
Single-parameter IF treats 0 as false, non-zero as true:
```c65
IF running
// Executes if running != 0
update()
ENDIF
```
### WHILE Loops
Loop while condition is true:
```c65
WHILE counter < 100
counter++
process(counter)
WEND
WHILE running
gameLoop()
WEND
WHILE x != y
x++
WEND
```
### FOR Loops
Loop with automatic counter:
```c65
FOR i = 0 TO 10
screen = i
NEXT
FOR x = 0 TO 255 STEP 2
result = result + x
NEXT
FOR counter = start TO finish
process(counter)
NEXT
```
### BREAK
Exit a loop early:
```c65
FOR i = 0 TO 100
IF i == 50
BREAK
ENDIF
process(i)
NEXT
WHILE running
IF error
BREAK
ENDIF
update()
WEND
```
---
## Functions
### Declaring Functions
```c65
FUNC initialize
BYTE temp = 0
screen = temp
FEND
FUNC setColor(color, brightness)
borderColor = color
backgroundColor = brightness
FEND
```
### Calling Functions
```c65
initialize()
setColor(1, 14)
process(xpos, ypos, spriteData)
```
### Parameter Passing Modes
Parameters can have different access modes:
- `in:` - Read-only (default if not specified)
- `out:` - Write-only (for returning values)
- `io:` - Read-write
```c65
FUNC add(in:a, in:b, out:result)
result = a + b
FEND
FUNC swap(io:x, io:y)
BYTE temp = x
x = y
y = temp
FEND
```
### Local Variables
Variables declared inside functions are local:
```c65
FUNC calculate(value)
BYTE local = 10 // Only exists inside this function
WORD temp // Local temporary
temp = value + local
FEND
```
### Curly Brace Syntax
Alternative parameter syntax using curly braces:
```c65
FUNC process({BYTE value} out:{BYTE result})
result = value + 1
FEND
```
### Returning from Functions
```c65
FUNC checkValue(value)
IF value == 0
EXIT // Return early
ENDIF
process(value)
FEND
```
---
## Memory Operations
### PEEK - Reading Memory
Read a byte from memory:
```c65
value = PEEK $D020 // Read from absolute address
char = PEEK screenPtr[index] // Read with offset
byte = PEEK pointer // Read from pointer
```
**Important:** For indexed access, the address must be a WORD variable in zero page.
```c65
WORD buffer @ $FB // Zero-page pointer
value = PEEK buffer[10] // Read buffer+10
```
### POKE - Writing Memory
Write a byte to memory:
```c65
POKE $D020 WITH 0 // Write to absolute address
POKE screenPtr[index] WITH char // Write with offset
POKE pointer WITH value // Write to pointer
```
### PEEKW - Reading 16-bit Words
Read a 16-bit value from memory:
```c65
WORD address
address = PEEKW $FFFC // Read reset vector
WORD buffer @ $FB
value = PEEKW buffer[10] // Read word at buffer+10
```
### POKEW - Writing 16-bit Words
Write a 16-bit value to memory:
```c65
POKEW $0314 WITH irqHandler // Set IRQ vector
POKEW dataPtr[0] WITH address // Write word with offset
```
### POINTER - Setting Pointers
Set a pointer to an address:
```c65
POINTER screenPtr TO $0400
POINTER bufferPtr TO dataBuffer
POINTER funcPtr TO myFunction
```
---
## Code Blocks
### ASM Blocks
Inline assembly for direct hardware control:
```c65
ASM
lda #$00
sta $d020
jsr $ffd2
ENDASM
```
#### Referencing Variables in ASM
Global variables can be referenced directly:
```c65
BYTE temp = 5
ASM
lda temp
clc
adc #10
sta temp
ENDASM
```
Local variables (inside FUNC) need pipe delimiters `|varname|`:
```c65
FUNC calculate(value)
BYTE local = 10
ASM
lda |local| // Reference local variable
clc
adc value // Global parameter
sta |local|
ENDASM
FEND
```
### SCRIPT Blocks
Generate assembly code at compile time using Starlark (Python-like):
```c65
// Generate a table of squares
SCRIPT
print("squares:")
for i in range(256):
print(" !8 %d" % (i * i % 256))
ENDSCRIPT
```
#### Sine Table Example
```c65
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
```
#### Referencing Variables
Use `|varname|` syntax in generated assembly:
```c65
BYTE tableSize = 64
SCRIPT
print("lookup:")
for i in range(64):
print(" !8 %d" % (i * 2))
print(" // Size stored in |tableSize|")
ENDSCRIPT
```
---
## Preprocessor
### Comments
```c65
// Single-line comment
BYTE counter = 0 // End-of-line comment
```
In ASM blocks, use assembly syntax:
```c65
ASM
lda #$00 ; Assembly comment
ENDASM
```
In SCRIPT blocks, use Python syntax:
```c65
SCRIPT
# Python-style comment
print("hello")
ENDSCRIPT
```
### Include Files
```c65
#INCLUDE mylib.c65 // Relative to current file
#INCLUDE lib/string.c65 // Subdirectory
#INCLUDE <stdlib.c65> // Search in C65LIBPATH
```
Include guards prevent multiple inclusion:
```c65
#IFNDEF __MY_LIBRARY
#DEFINE __MY_LIBRARY = 1
// Library code here
#IFEND
```
### Define Macros
Text substitution macros:
```c65
#DEFINE MAX_SPEED = 10
#DEFINE SCREEN = $$0400 // $$ escapes to literal $
speed = MAX_SPEED // Replaced with 10
```
Special characters in defines:
```c65
#DEFINE SPACE = $20 // Space character (hex 20)
#DEFINE NEWLINE = $0D // Carriage return
#DEFINE HEXADDR = $$D020 // Literal "$D020" text
```
**Note:** Prefer `BYTE CONST` and `WORD CONST` over `#DEFINE` for constants.
### Conditional Compilation
```c65
#IFDEF DEBUG
BYTE debugFlag = 1
#IFEND
#IFNDEF __LIB_INCLUDED
#DEFINE __LIB_INCLUDED = 1
// Include library code
#IFEND
```
### Pragma Directives
Control compiler behavior:
```c65
#PRAGMA _P_USE_LONG_JUMP 1 // Use JMP instead of branches
#PRAGMA _P_USE_IMMUTABLE_CODE 1 // No self-modifying code (for ROM)
#PRAGMA _P_USE_CBM_STRINGS 1 // Use PETSCII encoding
```
### Debug Directives
```c65
#PRINT Compiling main module // Print during compilation
#HALT // Stop compilation
```
---
## Writing Libraries
When creating a library file to be included by other programs, use this structure:
```c65
#IFNDEF __MY_LIBRARY
#DEFINE __MY_LIBRARY = 1
GOTO lib_mylib_skip // Jump over library code
// Library variables
WORD lib_mylib_buffer
// Library functions
FUNC lib_mylib_initialize
lib_mylib_buffer = $C000
FEND
FUNC lib_mylib_process(value)
// Do something
FEND
// Skip label - execution continues here after GOTO
LABEL lib_mylib_skip
#IFEND
```
**Key points for libraries:**
1. **Include guard** - Use `#IFNDEF` to prevent multiple inclusion
2. **GOTO skip** - Jump over all library code immediately
3. **LABEL skip** - Place at the end so GOTO jumps past everything
4. **Naming convention** - Prefix all names with `lib_yourlib_` to avoid conflicts
This ensures when someone does `#INCLUDE <mylib.c65>`, the library functions are defined but not executed.
---
## Common Patterns
### Complete Working Example
Here's a complete C64 program showing all the pieces together:
```c65
#INCLUDE <c64start.c65>
#INCLUDE <c64defs.c65>
GOTO start
// Variables
BYTE frameCount = 0
WORD screenPtr @ $FB
// Functions
FUNC initialize
BYTE borderColor @ $D020
borderColor = color_black
POINTER screenPtr TO $0400
FEND
FUNC updateScreen
BYTE color
color = frameCount & $0F
POKE screenPtr[0] WITH color
frameCount++
FEND
// Entry point
LABEL start
initialize()
WHILE 1
updateScreen()
WEND
```
### Screen Manipulation
```c65
WORD CONST SCREEN = $0400
WORD CONST COLOR_RAM = $D800
BYTE CONST SCREEN_WIDTH = 40
BYTE CONST SCREEN_HEIGHT = 25
FUNC clearScreen
WORD screenPtr @ $FB
POINTER screenPtr TO SCREEN
WORD remaining = 1000
WHILE remaining > 0
POKE screenPtr[0] WITH 32 // Space character
screenPtr++
remaining--
WEND
FEND
```
### String Handling
```c65
// Print null-terminated string
FUNC printString(textPtr)
BYTE char
char = PEEK textPtr[0]
WHILE char != 0
ASM
lda |char|
jsr $FFD2 // CHROUT
ENDASM
textPtr++
char = PEEK textPtr[0]
WEND
FEND
```
### Sprite Manipulation
```c65
WORD CONST VIC2 = $D000
BYTE spriteX @ VIC2+0
BYTE spriteY @ VIC2+1
BYTE spriteEnable @ VIC2+21
FUNC enableSprite(spriteNum)
BYTE mask
mask = 1
FOR i = 0 TO spriteNum
mask = mask * 2
NEXT
spriteEnable = spriteEnable | mask
FEND
```
### Zero Page Optimization
For frequently accessed pointers, use zero page:
```c65
WORD fastPtr @ $FB // Zero page = fast indexed access
FUNC processBuffer(buffer, size)
POINTER fastPtr TO buffer
WHILE size > 0
BYTE value
value = PEEK fastPtr[0]
// Process value
fastPtr++
size--
WEND
FEND
```
### Interrupt Handlers
```c65
WORD CONST IRQ_VECTOR = $0314
WORD oldIRQ
FUNC installIRQ
ASM
sei // Disable interrupts
ENDASM
oldIRQ = PEEKW IRQ_VECTOR
POKEW IRQ_VECTOR WITH myIRQ
ASM
cli // Enable interrupts
ENDASM
FEND
LABEL myIRQ
// IRQ handler code
ASM
// Save registers
pha
txa
pha
tya
pha
// Do IRQ work
inc $d020
// Restore and return
pla
tay
pla
tax
pla
rti
ENDASM
```
### Lookup Tables
Generate tables at compile time:
```c65
ASM
colorTable:
ENDASM
SCRIPT
colors = [0, 1, 15, 12, 11, 9, 2, 8]
for c in colors:
print(" !8 %d" % c)
ENDSCRIPT
```
### Delay Loops
```c65
FUNC delay(frames)
BYTE raster @ $D012
BYTE oldRaster
WHILE frames > 0
oldRaster = raster
WHILE raster == oldRaster
// Wait for raster to change
WEND
frames--
WEND
FEND
```
### Bit Manipulation
```c65
// Set bit
flags = flags | %00000001 // Set bit 0
// Clear bit
flags = flags & %11111110 // Clear bit 0
// Toggle bit
flags = flags ^ %00000001 // Toggle bit 0
// Test bit
IF flags & %00000001
// Bit 0 is set
ENDIF
```
---
## Best Practices
### 1. Use Constants for Magic Numbers
```c65
// Bad
POKE $D020 WITH 5
// Good
BYTE CONST COLOR_GREEN = 5
BYTE borderColor @ $D020
borderColor = COLOR_GREEN
```
### 2. Zero Page for Performance
Place frequently accessed pointers in zero page ($00-$FF):
```c65
WORD screenPtr @ $FB // Fast indexed access
WORD tempPtr @ $FD
```
### 3. Watch Expression Evaluation Order
Remember: left-to-right evaluation, no precedence!
```c65
// Be careful with expressions
result = 2 + 3 * 4 // = 20, not 14
// Use temps for clarity
temp = 3 * 4
result = 2 + temp // Now = 14
```
### 4. Include Guards
Always use include guards in library files:
```c65
#IFNDEF __MY_LIB
#DEFINE __MY_LIB = 1
// Library code
#IFEND
```
### 5. Comment Your Code
```c65
// Explain what and why, not how
FUNC calculateTrajectory(velocity, angle)
// Use fixed-point math (8.8 format)
// because we don't have floating point
WORD xVel
WORD yVel
// ...
FEND
```
### 6. Use Meaningful Names
```c65
// Bad
BYTE x
BYTE y
// Good
BYTE playerXPos
BYTE playerYPos
```
---
## Further Resources
- See `commands.md` for complete command reference
- See `syntax.md` for detailed syntax rules
- Check the `lib/` directory for example library code
- Set `C65LIBPATH` environment variable to specify library search paths
---
## Quick Reference Card
```c65
// Variables
BYTE varName = 10
WORD address = $C000
BYTE CONST MAX = 100
// Control Flow
IF x == 10
// code
ENDIF
WHILE x < 100
x++
WEND
FOR i = 0 TO 10
// code
NEXT
// Functions
FUNC myFunc(in:param1, out:result)
result = param1 + 1
FEND
// Memory
value = PEEK $D020
POKE $D020 WITH 5
address = PEEKW $FFFC
POKEW $0314 WITH handler
// Operators
+ - * / // Arithmetic
& | ^ // Bitwise
++ -- // Increment/Decrement
== != < > <= >= // Comparison
// Blocks
ASM
// assembly code
ENDASM
SCRIPT
# Python code
ENDSCRIPT
```