Language · guide

Programming in Stellar C

The compiler and its examples are on the Stellar C page; each Try it link below opens an example there, ready to compile and run.

  1. Compiling and running your own programs
  2. Your first program
  3. Numbers and variables
  4. Decisions and loops
  5. Functions
  6. Characters, strings and reading the keyboard
  7. Arrays
  8. Signed numbers
  9. Choosing with switch
  10. The terminal screen
  11. Keys, time and games
  12. The Elf2K's hardware
  13. How STELLAR shapes the language
  14. Faster code: asm { } and native { }
  15. When the compiler says no
  16. A complete program: Hi-Lo

Stellar C is a small C-like language for the RCA 1802. A Stellar C program compiles to STELLAR bytecode - the same .lst file a hand-written STELLAR program is - so it loads on an Elf2K running the STELLAR firmware, or runs in the web Simulator. This guide teaches it from the first line to a finished game. Every example in it is a complete program, and every one is compiled and run by SC/guide_check.py with the output shown here - so what you read is what you get.

If you know C, most of Stellar C will look familiar; the differences are the ones the 1802 and STELLAR impose, and section 12 explains them. If you do not know C, start at section 1.

0. Compiling and running your own programs

A Stellar C program is a plain text file, by convention named something.sc.

On the website - nothing to install. Open the Stellar C page on stellar-1802.dev.

  1. Type or paste your program into the Source box (it replaces the example; your edits are kept in the browser if you leave and come back - Revert brings the example back).
  2. Press Compile (Ctrl/Cmd+Enter compiles and runs). Errors show with their line number.
  3. Press Run. A program that reads keys: click the terminal and type. The input box under the example menu types keys for you before the program starts.
  4. Download .lst saves the compiled program for a real Elf2K.

Every example in this guide has a Try it link that opens it there.

On your computer - Python 3 and the work repository (stellar-1802):

python3 SC/scc.py myprog.sc          # writes myprog.lst beside it (and myprog.sym.json)

Then either load myprog.lst into the Elf2K - its D-lines go to the monitor like any STELLAR app - and cold-boot STELLAR; or open it in the web Simulator (Open .lst..., or drag the file onto the page) and press Run. The .lst header says how many STELLAR commands it compiled to and how much memory it uses; below that is the compiled program, each command beside the source line it came from.


1. Your first program

hello.scTry it →
void main() {
    print("Hello from the 1802!\r\n");
    print("2 + 3 = ");
    printu(2 + 3);
    print("\r\n");
}
Output
Hello from the 1802!
2 + 3 = 5

2. Numbers and variables

numbers.scTry it →
const DOZEN = 12;
u16 eggs, boxes, left;

void main() {
    eggs = 100;
    boxes = eggs / DOZEN;
    left = eggs % DOZEN;
    printu(eggs); print(" eggs make "); printu(boxes);
    print(" boxes, with "); printu(left); print(" left over.\r\n");
    eggs = 65535;
    eggs = eggs + 1;
    print("65535 + 1 = "); printu(eggs); print("\r\n");
}
Output
100 eggs make 8 boxes, with 4 left over.
65535 + 1 = 0

3. Decisions and loops

fizzbuzz.scTry it →
u16 n;

void main() {
    for (n = 1; n <= 15; n++) {
        if (n % 15 == 0) print("FizzBuzz");
        else if (n % 3 == 0) print("Fizz");
        else if (n % 5 == 0) print("Buzz");
        else printu(n);
        print(" ");
    }
    print("\r\n");
}
Output
1 2 Fizz 4 Buzz Fizz 7 8 Fizz Buzz 11 Fizz 13 14 FizzBuzz 
loops.scTry it →
u16 i, total;

void main() {
    total = 0;
    i = 0;
    while (1) {
        i++;
        if (i > 20) break;
        if (i % 2 == 1) continue;
        total += i;
    }
    print("2 + 4 + ... + 20 = "); printu(total); print("\r\n");
}
Output
2 + 4 + ... + 20 = 110

4. Functions

functions.scTry it →
u16 gcd(u16 a, u16 b) {
    u16 t;
    while (b != 0) {
        t = a % b;
        a = b;
        b = t;
    }
    return a;
}

u16 isprime(u16 n) {
    u16 d;
    if (n < 2) return 0;
    for (d = 2; d * d <= n; d++)
        if (n % d == 0) return 0;
    return 1;
}

void main() {
    u16 n;
    print("gcd(1071, 462) = "); printu(gcd(1071, 462)); print("\r\n");
    print("primes below 40:");
    for (n = 1; n < 40; n++)
        if (isprime(n)) { print(" "); printu(n); }
    print("\r\n");
}
Output
gcd(1071, 462) = 21
primes below 40: 2 3 5 7 11 13 17 19 23 29 31 37

5. Characters, strings and reading the keyboard

echo.scTry it →
u16 k, count;

void main() {
    print("Type, and press Enter: ");
    count = 0;
    while (1) {
        k = getc();
        if (k == '\r') break;
        if (k >= 'a' && k <= 'z') k = k - 'a' + 'A';
        putc(k);
        count++;
    }
    print("\r\n"); printu(count); print(" characters\r\n");
}
Output
Type, and press Enter: HELLO
5 characters

A string is an array of u8 (bytes) ending with a 0. getline reads a whole line into one, with Backspace working, and stops at the array's size so a long line cannot overrun it:

names.scTry it →
u8 name[16];
u16 n, i;

void main() {
    print("Your name? ");
    n = getline(name);
    print("Hello, "); print(name); print(" - backwards: ");
    i = n;
    while (i > 0) { i--; putc(name[i]); }
    print("\r\n");
}
Output
Your name? Ada Lovelace
Hello, Ada Lovelace - backwards: ecalevoL adA

6. Arrays

arrays.scTry it →
u8  counts[6];
u16 scores[] = { 300, 1200, 50, 700, 950 };
u16 i, j, t;

void main() {
    // a histogram of 600 dice rolls
    for (i = 0; i < 600; i++) counts[rand() % 6]++;
    t = 0;
    for (i = 0; i < 6; i++) t += counts[i];
    print("rolls counted: "); printu(t); print("\r\n");
    // sort the scores, smallest first (a bubble sort)
    for (i = 0; i < 5; i++)
        for (j = 0; j + 1 < 5 - i; j++)
            if (scores[j] > scores[j + 1]) {
                t = scores[j]; scores[j] = scores[j + 1]; scores[j + 1] = t;
            }
    for (i = 0; i < 5; i++) { printu(scores[i]); print(" "); }
    print("\r\n");
}
Output
rolls counted: 600
50 300 700 950 1200 

7. Signed numbers

signed.scTry it →
i16 t[] = { -12, 5, -3, 20, -8, 0, 15 };
i16 low, high, sum, x;
u16 i;

void main() {
    low = t[0]; high = t[0]; sum = 0;
    for (i = 0; i < 7; i++) {
        if (t[i] < low) low = t[i];
        if (t[i] > high) high = t[i];
        sum += t[i];
    }
    print("lowest "); printi(low); print(", highest "); printi(high);
    print(", average "); printi(sum / 7); print(" remainder "); printi(sum % 7); print("\r\n");
    x = -7;
    print("x / 2 = "); printi(x / 2); print(", x % 2 = "); printi(x % 2);
    print(", x >> 1 = "); printi(x >> 1); print(", -7 / 2 = "); printi(-7 / 2); print("\r\n");
}
Output
lowest -12, highest 20, average 2 remainder 3
x / 2 = -3, x % 2 = -1, x >> 1 = -4, -7 / 2 = -3

8. Choosing with switch

switch.scTry it →
u16 k, a, b;

void main() {
    a = 20; b = 4;
    while (1) {
        k = getc();
        switch (k) {
            case '+': printu(a + b); break;
            case '-': printu(a - b); break;
            case '*': printu(a * b); break;
            case '/': printu(a / b); break;
            case 'q': case 'x': print("bye"); return;
            default: print("?");
        }
        print(" ");
    }
}
Output
24 16 80 5 bye

9. The terminal screen

The Elf2K's terminal is a VT100: text can be placed anywhere, shown in reverse video, erased.

screen.scTry it →
u16 r, n;

void main() {
    cls();
    at(1, 1); rev(1); print(" REPORT "); rev(0);
    at(3, 3); print("row   value   padded   hex");
    for (r = 0; r < 4; r++) {
        n = r * 1234 + 5;
        at(4 + r, 3); printu(r);
        at(4 + r, 9); printw(n, 5);
        at(4 + r, 17); printz(n, 5);
        at(4 + r, 26); printh(n);
    }
    at(9, 1);
}
The screen
 REPORT

  row   value   padded   hex
  0         5   00005    0005
  1      1239   01239    04D7
  2      2473   02473    09A9
  3      3707   03707    0E7B
calldoes
cls()clear the screen, cursor to the top left
at(row, col)move the cursor (rows 1-24, columns 1-80)
rev(1) / rev(0)reverse video on / off
eol()erase from the cursor to the end of the line
cursor(0) / cursor(1)hide / show the cursor (hide it while a game draws)
printw(e, n)e right-aligned in n columns
printz(e, n)e as n digits with leading zeros: printz(7, 2) is 07
printh(e) / printh(e, n)e in hexadecimal, 4 or n digits

The terminal does NOT scroll when something is written on the bottom row - draw a full-screen program with at() rather than by printing new lines.


10. Keys, time and games

A game is a loop: look for a key without waiting, move things, draw, wait a little, repeat.

game.scTry it →
u16 x, y, k;

u16 key() {                     // a key: 'U' 'D' 'L' 'R' for the arrows, 0 if none
    if (!keyready()) return 0;
    k = getc();
    if (k != 27) return k;      // not ESC: an ordinary key
    k = getc();
    if (k == '[') k = getc();   // ESC [ A ... - the '[' may have been lost (see below)
    switch (k) {
        case 'A': return 'U';
        case 'B': return 'D';
        case 'C': return 'R';
        case 'D': return 'L';
    }
    return 0;
}

void main() {
    x = 10; y = 5;
    cls(); cursor(0);
    at(1, 1); print("Arrows move the star, q quits");
    while (1) {
        at(y, x); putc('*');
        delay(10);
        k = key();
        if (k == 'q') break;
        if (k == 0) continue;
        at(y, x); putc(' ');
        switch (k) {
            case 'U': if (y > 2) y--; break;
            case 'D': if (y < 23) y++; break;
            case 'L': if (x > 1) x--; break;
            case 'R': if (x < 79) x++; break;
        }
    }
    cursor(1); at(24, 1);
}
The screen
Arrows move the star, q quits




            *

11. The Elf2K's hardware

The front panel. out(e) shows a byte on the two-digit hex display; q(1) / q(0) turns the Q lamp (and anything wired to Q - a speaker) on and off.

The INPUT switch is EF line 4: ef(4) is 1 while it is held; waitef(4) waits for one press and release (the other VMs keep running meanwhile).

The clock. rtcget(t) fills an 8-byte u8 array with the date and time from the Elf2K's DS12887 clock chip (firmware V0061.35 or later):

clock.scTry it →
u8 t[8];

void main() {
    rtcget(t);
    print("Today is ");
    printu(t[6] * 256 + t[7]); putc('-'); printz(t[5], 2); putc('-'); printz(t[4], 2);
    print(", the time is ");
    printz(t[2], 2); putc(':'); printz(t[1], 2); putc(':'); printz(t[0], 2);
    print("\r\n");
}
Output
Today is 2026-10-04, the time is 14:23:07
t[0]t[1]t[2]t[3]t[4]t[5]t[6], t[7]
secondminutehourweekday, 0 = Sundaydaymonthyear, high and low byte

ok = rtcset(t) sets the clock from the same layout and returns 1 - or 0 if the date is impossible (31 February), in which case nothing is changed.

Memory outside the program. peek(a) and poke(a, v) read and write a byte at an absolute address; peekw / pokew a 16-bit word. Programs normally never need them - they are for sharing data with other programs (Time Grab Store's log at $4B00, say). Never write at $8000 or above: that is the Elf2K's own firmware.


12. How STELLAR shapes the language

Stellar C is small because the machine is small. Knowing why saves surprises.


13. Faster code: asm { } and native { }

Most programs never need these. When one inner loop must be faster, you can write it in STELLAR commands (asm) or in raw 1802 machine code (native, about 70 times faster than compiled code).

asmdemo.scTry it →
u16 n, total;

void main() {
    n = 10; total = 0;
    asm {
    loop:
        ADDXYZ {total}{n} 0{total}     // total = total + n
        DECX 0{n}
        TESTXZ 0{n}
        IFFALSEI @loop
    }
    print("10 + 9 + ... + 1 = "); printu(total); print("\r\n");
}
Output
10 + 9 + ... + 1 = 55

14. When the compiler says no

Compile errors name the line and say what is wrong. The common ones:

recursion.scdoes not compile
u16 fact(u16 n) {
    if (n < 2) return 1;
    return n * fact(n - 1);
}
void main() { printu(fact(5)); }
The compiler says
fact() is recursive

A function that calls itself - write it as a loop instead.

toodeep.scdoes not compile
u16 a, b, c, d, e, f, x;
void main() { x = (a * b + c) / (d * e + f * (a + b)); }
The compiler says
expression too deep

More than three temporary results at once - split the expression with a helper variable.

badcase.scdoes not compile
u16 k;
void main() {
    k = 1;
    switch (k) { case 1: k = 2; case 1: k = 3; }
}
The compiler says
case 1 twice
messagemeans
expected ;, found ...a missing ;, ) or } just before that point
unknown variable 'x'not declared (or declared in another function)
no function f()a call to a function that does not exist
f() takes 2 argumentsthe wrong number of arguments
... can only be a statement of its ownat(), printz(), printw(), printh() inside an expression
getline(buf): buf must be a u8 arraygetline and rtcget need a byte array
break outside a loopbreak / continue with no loop (or continue in a switch with no loop)
the program needs 16 KBthe program is bigger than three 4 KB blocks

15. A complete program: Hi-Lo

Everything together: a function, a loop, getline, a switch, the terminal.

hilo.scTry it →
u8  line[6];
u16 secret, guess, tries, n, i, ok;

u16 number() {                  // the digits typed, as a number (65535 if there are none)
    n = 0; ok = 0;
    for (i = 0; line[i] != 0; i++)
        if (line[i] >= '0' && line[i] <= '9') { n = n * 10 + line[i] - '0'; ok = 1; }
    if (!ok) return 65535;
    return n;
}

void main() {
    while (1) {
        secret = rand() % 100 + 1;
        tries = 0;
        print("\r\nI have a number from 1 to 100.\r\n");
        while (1) {
            print("Your guess? ");
            getline(line);
            guess = number();
            if (guess == 65535) continue;
            tries++;
            if (guess < secret) print("  higher\r\n");
            else if (guess > secret) print("  lower\r\n");
            else break;
        }
        print("Yes! "); printu(secret); print(" in "); printu(tries); print(" tries.\r\n");
        print("Again (y/n)? ");
        getline(line);
        switch (line[0]) {
            case 'y': case 'Y': continue;
        }
        print("Bye.\r\n");
        return;
    }
}
Output
I have a number from 1 to 100.
Your guess? 50
  lower
Your guess? 25
  lower
Your guess? abc
Your guess? 12
  higher
Your guess? 18
  higher
Your guess? 21
  higher
Your guess? 23
Yes! 23 in 6 tries.
Again (y/n)? n
Bye.

That game was played by the guide's checker, typing the guesses in. rand() decides the secret, so a game on your machine will be a different one. Notice that abc was simply asked again: number() returns 65535 when no digit was typed, and continue goes round for another guess without counting it as a try.


Quick reference