Skip to content

Stdio

Stdio is the screen-I/O class. It writes characters to the active text-mode screen, positions the cursor, and offers a printf-style formatter that handles every primitive type plus structs and classes.

#import <Stdio.xt>

All methods are static. The class is typically promoted with use Stdio; or the #use Stdio shorthand so callers can write print(...) and printf(...) bare — but every method is reachable in its qualified Stdio.xxx form too.

static void putChar(u8 ch); // emit one character
static void scroll(void); // scroll the screen up one line
static void setCursor(u8 x, u8 y); // move cursor to column x, row y
Stdio.putChar('H');
Stdio.putChar('i');
Stdio.putChar('\n');
Stdio.setCursor(10, 5);
Stdio.print("centred-ish\n");

Stdio.print is overloaded across every primitive xtc type. The compiler picks the right implementation based on the argument’s type:

static void print(string s);
static void print(u16 v);
static void print(i16 v);
static void print(u32 v);
static void print(i32 v);
static void print(float f);
static void print(double d);
Stdio.print("hello\n");
Stdio.print((u16)1000);
Stdio.print((i32)-50000);
Stdio.print((float)3.14);

print does not append a newline — supply one explicitly when needed.

There’s no print(u8) overload because u8 widens implicitly to u16. Pass u8 values directly; the compiler promotes for you.

static void printHex(u8 n); // 2 hex digits
static void printHex(u16 v); // 4 hex digits
static void printHex(u32 v); // 8 hex digits
Stdio.printHex((u8)$2A); // 2A
Stdio.printHex((u16)$1234); // 1234
Stdio.printHex((u32)$DEADBEEF); // DEADBEEF

Output is uppercase, left-zero-padded to the type’s full width — no $ prefix is emitted.

static void printf(string fmt, ...);
static void printfAt(u8 x, u8 y, string fmt, ...);

printfAt is setCursor(x, y) followed by printf — convenient for table-style screens.

SpecifierArgument typeOutput
%di16signed decimal
%uu16unsigned decimal
%xu16hex, 4 digits
%ldi32signed decimal, 32-bit
%luu32unsigned decimal, 32-bit
%lxu32hex, 8 digits
%ffloatfloating-point
%lfdoubledouble-precision floating-point
%cu8character (no width promotion)
%sstring (u8@)null-terminated string
%eenum value (must be statically typed as an enum)textual name of the enum value — rewritten to %s at compile time
%@struct or classrecursive print of every member
%%literal %
u16 score = 1234;
i32 millis = -50000;
float pi = 3.14159;
string name = "Player 1";
Stdio.printf("%s scored %u in %ld ms\n", name, score, millis);
Stdio.printf("pi ≈ %f\n", pi);
Stdio.printf("ratio: %u%%\n", (u16)42); // "ratio: 42%"

%e prints the textual name of an enum value. The translation happens entirely at compile time — the runtime printf never sees a %e:

  1. The compiler scans every printf / printfAt format string at the call site.
  2. Each %e is rewritten in place to %s.
  3. The compiler generates a small _enum_lookup_<EnumName>(value) helper for any enum reached by a %e, and the call site emits a JSR to that helper. The helper returns a string pointer, which gets packed as the matching %s argument.

So at the level of the binary, every %e becomes an ordinary %s call, and the cost of the feature is one helper per enum (emitted exactly once, regardless of how many call sites reach it).

enum direction = {N = 1, E, S, W};
direction d = E;
Stdio.printf("heading: %e\n", d); // heading: E
Stdio.printf("raw : %u\n", (u16)d); // raw : 2

The argument paired with %e must be statically typed as an enum. A non-enum argument paired with %e (a plain u8, a result of arithmetic, an integer cast away from the enum type) is a compile-time error:

error: printf '%e' requires an enum argument (got 'u8')

If you want the underlying numeric value, use %u or %d and cast explicitly — there’s no silent fallback from %e to integer-print, by design.

The compiler scans every printf format string in the program at compile time and only links in the specifier handlers that are actually used. A program that only prints strings and u16s pays for %s and %u — none of the floating-point, double, or %@ machinery makes it into the binary. You can also force-include or force-exclude specifiers via -DHAS_FFMT=1, -DHAS_LFMT=0, etc.

typedef struct {
u16 x;
u16 y;
u8 tint;
} Sprite;
Sprite s = {160, 96, 7};
Stdio.printf("sprite=%@\n", s);
// sprite=(160, 96, 7)

Nested structs are formatted recursively with parentheses around each level. The mechanism uses the compiler-generated descriptor for the struct, so user code doesn’t need to write any printer.

Every variadic call shares a single 64-byte pack buffer (the address is platform-specific — see Functions → Shared pack buffer). For printf that means:

  • Total argument payload per call ≤ 62 bytes (64 minus the 2-byte fmt-pointer header).
  • A printf call inside another variadic that has called va_start will scramble the outer’s buffer — sema diagnoses this at compile time.
  • printfAt is a pure forwarder: it does not call va_start itself, so it’s exempt from the reentrance check.

Stdio is reimplemented per-platform. The Atari version writes through the OS character output channel; the Commodore 64 version (under support/commodore/lib/Stdio.xt) drives the C64 KERNAL. The class API stays identical — same method signatures, same format specifiers — so a program that only uses Stdio for output is portable between the two without source changes.