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.
Basic output
Section titled “Basic output”static void putChar(u8 ch); // emit one characterstatic void scroll(void); // scroll the screen up one linestatic void setCursor(u8 x, u8 y); // move cursor to column x, row yStdio.putChar('H');Stdio.putChar('i');Stdio.putChar('\n');Stdio.setCursor(10, 5);Stdio.print("centred-ish\n");print — type-overloaded direct printing
Section titled “print — type-overloaded direct printing”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.
printHex — type-overloaded hex printing
Section titled “printHex — type-overloaded hex printing”static void printHex(u8 n); // 2 hex digitsstatic void printHex(u16 v); // 4 hex digitsstatic void printHex(u32 v); // 8 hex digitsStdio.printHex((u8)$2A); // 2AStdio.printHex((u16)$1234); // 1234Stdio.printHex((u32)$DEADBEEF); // DEADBEEFOutput is uppercase, left-zero-padded to the type’s full width — no $ prefix is emitted.
printf — formatted print
Section titled “printf — formatted print”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.
Format specifiers
Section titled “Format specifiers”| Specifier | Argument type | Output |
|---|---|---|
%d | i16 | signed decimal |
%u | u16 | unsigned decimal |
%x | u16 | hex, 4 digits |
%ld | i32 | signed decimal, 32-bit |
%lu | u32 | unsigned decimal, 32-bit |
%lx | u32 | hex, 8 digits |
%f | float | floating-point |
%lf | double | double-precision floating-point |
%c | u8 | character (no width promotion) |
%s | string (u8@) | null-terminated string |
%e | enum value (must be statically typed as an enum) | textual name of the enum value — rewritten to %s at compile time |
%@ | struct or class | recursive 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%"Enum names: %e
Section titled “Enum names: %e”%e prints the textual name of an enum value. The translation happens entirely at compile time — the runtime printf never sees a %e:
- The compiler scans every
printf/printfAtformat string at the call site. - Each
%eis rewritten in place to%s. - The compiler generates a small
_enum_lookup_<EnumName>(value)helper for any enum reached by a%e, and the call site emits aJSRto that helper. The helper returns a string pointer, which gets packed as the matching%sargument.
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: EStdio.printf("raw : %u\n", (u16)d); // raw : 2The 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.
Pre-scanning and code-size gating
Section titled “Pre-scanning and code-size gating”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.
Recursive struct print: %@
Section titled “Recursive struct print: %@”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.
Variadic limits
Section titled “Variadic limits”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
printfcall inside another variadic that has calledva_startwill scramble the outer’s buffer — sema diagnoses this at compile time. printfAtis a pure forwarder: it does not callva_startitself, so it’s exempt from the reentrance check.
Platform notes
Section titled “Platform notes”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.