--- name: powerc description: > Use this skill when writing, compiling, linking, or running C programs with Spinnaker's Power C on the Commodore 64 (reference/POWERc.d81 under VICE). Covers the C dialect limits (K&R style, 2-byte int, 5-byte float, no bit fields), the library, the Shell, the ed/ced editors, the cc/trim/link toolchain, PETSCII source files, calling machine language with sys(), and how to automate the whole build in VICE. Source: Power C Instruction Manual (Spinnaker, 1986); verified in VICE 3.10. --- # Power C on the Commodore 64 Power C is a disk-based native-code C compiler. Everything runs from a Shell (`$` prompt) on the disk image `reference/POWERc.d81`. For emulator mechanics (keybuf, warp, screenshots) load `skills/vice-emulator/SKILL.md`; this skill is the C side. ## Language: what to write Power C is **K&R C**, not ANSI C. Write code that way or it will not compile. ```c #include main() { int i; for (i = 1; i <= 5; i++) printf("%d squared is %d\n", i, i * i); } ``` - **K&R function definitions**: parameter types go after the parameter list (`add3(a, b, c) int a, b, c; { ... }`). No prototypes, no `void`. - Functions returning anything other than `int` must be declared in the calling file (`char *malloc(); float atof();`). - **Type sizes**: `char` 1, `short`/`int`/`long`/`unsigned`/pointer 2, `float`/`double` 5 (BASIC float format). `short`, `int`, `long` are synonyms; so are `float` and `double`. - Integers are stored low byte first. Chars are not sign-extended; `>>` does not sign-fill (vacated bits are always 0). - Identifiers are significant to **8 characters** (goto labels to 7). - **Not supported**: bit fields (use shifts and masks), static pointer initialisers other than strings (`static char *s = "x";` is fine, `static int *p = &x;` is not), multi-character constants (only the first char is used). - Conditional operator does not unify operand types: cast explicitly (`x = a == b ? f : (float) i;`). - `||`, `?:`, `=` and `,` inside subscripts/constant expressions need parentheses: `a[(i > j ? i : j)]`. - String constants are limited to 255 characters. - `register` is ignored; the first 32 bytes of a function's ints/chars/pointers live in zero page, so **declare hot variables first**. - Use `unsigned` for values that are never negative (especially array indices); it is faster. - `#include ` and `#include "x"` are identical, and the header must be on the **same disk as the source** (copy `stdio.h`, `math.h` etc. to your work disk). - Large sources can overflow the compiler: split into several `.c` files, compile each, and give all the `.o` files to the linker. - Keyboard EOF is a `.` on an otherwise blank line. ## Typing special characters (real C64 keyboard / in ced) | Char | Keys | |------|------| | `{` `}` | Shift+`+`, Shift+`-` | | `\` | English pound key | | `~` | Logo+`P` | | `_` | Logo+`@` | | `\|` | Logo+`*` | ## Source file format on disk Source files are **SEQ files in PETSCII with CR line endings**. When writing them from the host (e.g. with `c1541 -write`), convert: - ASCII `a-z` -> `$41-$5A`, ASCII `A-Z` -> `$C1-$DA` - `{` -> `$DB`, `}` -> `$DD` - newline -> `$0D` ``` c1541 work.d81 -write src.c "squares.c,s" c1541 work.d81 -read "hello.c,s" out.c # inspect an existing source ``` Filenames end in `.c`; compiler output ends in `.o`; Shell programs end in `.sh`. ## The Shell (C64) Run it with `load"*",8` + `run` (or autostart `shell`). Prompt is `$`. | Command | Meaning | |---------|---------| | `bye` | exit to BASIC | | `l [pat]` / `ls [pat]` | list work disk / system disk | | `rm file` | scratch a file on the work disk | | `mv a b` | rename | | `pr file` / `pr >> file` | list a file to screen / to printer (device 4) | | `disk cmd` | send a DOS command (`disk n0:name,id` formats) | | `work [dev drive]` | show or set the work disk | | `load cmd` | load a command without running it | | `ed [file]`, `ced [file]` | editor / syntax-checking editor | | `cc [-p] file.c` | compile | | `trim file.o` | optimise the object file (3-8% smaller) | | `link [-s [addr]]` | link | Programs named `X.sh` run under the Shell by typing `X` plus arguments; stdin/stdout can be redirected to disk files like UNIX. Included utilities: `sort [-n]`, `find [-x] [-n] [-f] pattern [files]`, `wfreq`, `format`, `print`, `trim`. ## Editor (ed / ced) `ced` = `ed` plus a `CHeck` command that checks C syntax. Start it with `ced file.c`. Edit mode: cursor keys move; F1/F2 page down/up; F3/F4 search down/up; F5 cut (Shift+Run/Stop starts select mode, cursor keys set the range, Return accepts); F6 paste; F7/F8 end/start of line; Clr/Home bottom of buffer; Shift+Clr/Home top; Shift+Return opens a line; **Run/Stop enters command mode**. Command mode: `dir`, `disk cmd`, `GEt file`, `PUt file`, `PRint`, `GOto buf`, `List`, `CHeck` (ced only), `QUIT`, `CLEAR [buf]`, `/str` (search), `/dog/cat` (replace next), `/dog/cat/` (replace all). Return goes back to edit mode. Saving: Run/Stop, then `PUt filename.c`, Return, then `QUIT`. ## Build pipeline ``` $ cc squares.c -> squares.o (name must end in .c) $ trim squares.o (optional) $ link -s (BASIC-runnable) or link (Shell program, name must end .sh) > squares.o object file(s), one per line > ^ up-arrow: link stdlib.l + syslib.l automatically > empty RETURN = done output file name: squares ``` - `cc` prompts "insert source/compiler/object disk": with a single image just press RETURN. - `link -s` with no address links at the start of BASIC, so `load"squares",8` + `run` works. With an address (`link -s $c000` or decimal) you must `load"x",8,1` and `SYS` the address. - `link` takes **no filename argument**; names are entered at the `>` prompt. `x` + RETURN aborts. - The linker only accepts `.o`, `.obj`, `.l` names. Maths functions need `math.l` too (`>math.l`) and `#include `. - **"unresolved external references"** (`printf`, `c$start`, ...) = you forgot the `^` library step. - **"63, file exists"** = scratch the old output first (`rm squares`, or `c1541 -delete`). - The up-arrow key is `^` in VICE `-keybuf`. ## Library quick reference Link `stdlib.l` and `syslib.l` via `^`; `math.l` by name. - **stdio.h**: `printf fprintf sprintf scanf fscanf sscanf getchar putchar gets puts fgets fputs getc putc fgetc fputc getw putw fopen freopen fclose fread fwrite feof ferror`. `EOF` is -1, `NULL` 0, `stdin` 1, `stdout` 2; `FILE` is an int. - **printf** conversions: `%d %u %o %x %f %s %c %%`, with `-`, width, `.precision`, `*`. Max width 64. - **scanf** conversions: `%d %x %o %f %s %c`, `*` suppress, width. - **fopen** modes `"r"`/`"w"`; program files `"name,p"`; replace with `"@0:name,p"`. Always check `ferror()` after fopen. - **BASIC-style I/O**: `open(fileno, device, secaddr, name)`, `close(fileno)`; use file numbers 5-9 alongside fopen. - **Strings** (`#include `): `strcat strncat strcmp strncmp strcpy strncpy strlen index rindex`. - **Memory**: `malloc calloc realloc free` (declare `char *malloc();`); `bcopy bcmp bzero ffs`. - **Classification**: `isalpha isupper islower isdigit isalnum isspace ispunct isprint iscntrl isascii`. - **Convert**: `atoi atof abs fabs`. **Random**: `random()`, `srandom(seed)`. **Sort**: `qsort(base, n, size, cmp)`. - **Control**: `exit() abort() setjmp longjmp` (`#include `), `highmem(addr)`. - **Directory** (`#include `): `opendir readdir rewinddir closedir`. - **Math** (`#include `, link `math.l`): `sin cos tan asin acos atan atan2 sinh cosh tanh exp log log10 pow sqrt floor ceil modf frexp ldexp hypot cabs`. ## Memory and machine-language interface (C64) - Compiled programs and the heap/stack end at `$D000` by default (highest usable address `$CFFF`). `highmem(0xffff)` lets a program use all RAM; call `highmem(0xc000)` if you place machine code at `$C000`. - Zero page `20-33`, `43-74`, `253-254` (decimal) are reserved by the system; `34-42`, `75-96`, `251-252` are scratch and safe for your ML routines. - The tape buffer `$033C-$03FB` is used by the system; do not touch it. - Call machine code on the **C64** with `sys(address, &a, &x, &y)` -- no bank argument. The manual's library page shows the **C128** form `sys(bank, address, ...)` (bank 15); used on a C64 it jumps to address 15 and warm-starts the machine (verified in VICE). A/X/Y are loaded from the pointers and written back afterwards; it returns the carry flag. Example: `char a, x, y; a = 147; sys(0xffd2, &a, &x, &y);` clears the screen via CHROUT. - Pass multiple args to an ML routine by declaring one `float args;` parameter so they stay contiguous, and hand the routine the address of that block in X/Y. ## Running and testing under VICE ```bat rem compile powerc-build.bat "cc squares.c\n" rem link (delete any old output first: c1541 work.d81 -delete squares) powerc-build.bat "link -s\nsquares.o\n^\n\nsquares\n" rem run x64sc -autostart "work.d81:squares" -autostart-warp ``` Work on a **copy** of `reference/POWERc.d81` (`examples/powerc-work.d81`); VICE writes persist. Scripts: `examples/powerc-ced.bat`, `examples/powerc-build.bat`. Full emulator details are in `skills/vice-emulator/SKILL.md` ("Automating Disk-Based Tools"). ## Pitfalls checklist 1. ANSI syntax (prototypes, `void`, `//` comments) will fail; use K&R and `/* */`. 2. `cc` needs the `.c` extension; `link` needs the `^` step and a fresh output name. 3. `\r` in `-keybuf` types a literal `r`; use `\n`. 4. Headers missing from the work disk give compile errors. 5. The 8-character identifier limit can make two long names collide. 6. `int` is 16-bit and right shifts do not sign-fill; do not rely on sign extension. 7. Source files are PETSCII, so host text files must be converted before use.