TonicBoxOS · language reference

TBC - a restricted C

TBC is the small C dialect that all of TonicBoxOS is written in - the shell, the coreutils, the editor, and the toolchain itself. It compiles to the TB32 instruction set and runs inside the in-browser VM. The compiler is self-hosting: an in-guest cc compiles the very same dialect, byte-for-byte identical to the build-time compiler.

target TB32 / TBX objects toolchain cc → as model accumulator codegen self-hosting yes

00What it is

TBC is C, minus most of C. It keeps the shape of the language - functions, pointers, N-D arrays, the full operator set, the familiar control flow, a working preprocessor - but drops the heavy parts: no structs, no floats, no full standard library. The goal was a language small enough to write a compiler for in the language itself, yet real enough to build an operating system's userland in.

The honest one-liner: C with only int and char, no structs/floats, a minimal built-in prelude instead of a full stdlib, and unsigned / % >>. Everything below spells that out.

01What it supports

AreaStatusNotes
Types int, charyes32-bit int; 1-byte char (read unsigned)
Pointers, &x *pyesany depth; correct pointer arithmetic on declared pointers/arrays
Arraysyesa[i] and multi-dimensional a[i][j]; brace initializers incl. unsized int a[] = {1,2,3} and char s[] = "hi"
Functions, recursionyesargs passed in registers; prototypes / forward declarations OK
Function pointersyesint (*fp)(int,int) vars, globals, callback params; untyped (address only)
Control flowyesif/else while for do/while switch/case break continue goto return
Operatorsyes+ - * / % & | ^ << >> == != < <= > >= && || ! ~ ?: ,, ++ --, += -= *= /= %= &= |= ^=
sizeofyessizeof(int), sizeof(char*), and sizeof expr
enum, _Boolyesnamed constants; _Bool aliases int
staticyesfile-scope statics and static locals (value kept across calls)
typedefyesaliases for scalar, pointer, array, and function-pointer types
Preprocessoryesobject & function-like #define, #undef, conditionals #if/#ifdef/#ifndef/#elif/#else/#endif with defined() and constant expressions
Headers, multi-fileyessplit a program across .h files with textual #include "file.h" (resolved relative to the including file; #ifndef guards work) - one translation unit, no linker
Literalsyesdecimal, 0x hex, 0b binary, 0 octal, U/L suffixes, full char/string escapes
Commentsyes// and /* */

02The prelude

There is no libc. Every program is compiled with a small built-in prelude of thin syscall wrappers, a handful of memory/string helpers, and the raw __sys(num, a, b, ...) intrinsic underneath. Prelude functions your program never uses are stripped by dead-code elimination, so the prelude is zero-cost - anything beyond it (formatting, custom containers) you write yourself.

int write(int fd, char* buf, int n);   int read(int fd, char* buf, int n);
int open(char* p, int fl);       int close(int fd);
int stat(char* p, char* buf);    int listdir(char* p, char* b, int n);
int getcwd(char* b, int n);      int chdir(char* p);
int getuid();  int geteuid();     int unlink(char* p);
int mkdir(char* p, int m);      int rmdir(char* p);   int rename(char* a, char* b);
int chmod(char* p, int m);      int chown(char* p, int u);
int strlen(char* s);            void exit(int c);
char* sbrk(int d);   char* malloc(int n);   void free(char* p);
char* memcpy(char* d, char* s, int n);   char* memset(char* d, int c, int n);   int memcmp(char* a, char* b, int n);
char* strcpy(char* d, char* s);   char* strcat(char* d, char* s);   char* strchr(char* s, int c);
int strcmp(char* a, char* b);   int strncmp(char* a, char* b, int n);
int fork();   int getpid();   int getppid();   int waitpid(int pid, int st, int opt);
int pipe(int fds);   int dup(int fd);   int dup2(int of, int nf);
int execve(char* p);   int execvp(char* p, int v, int n);   int kill(int pid, int sig);
int set_raw(int on);   int read_nb(int fd, char* buf, int len);   int ioctl(int fd, int req, char* arg);
int msleep(int ms);   int getrandom(int* p, int n);
// ...plus srun and __sys(num, ...) for anything else

The standard-library reference documents every one of these - parameters, return values, error codes, and the syscall each maps to. To reach a syscall the prelude doesn't wrap, call __sys directly; the syscall reference lists every number and its signature.

03A whole program

Word count, in idiomatic TBC: globals for buffers, everything hand-rolled, and the exit code standing in for output.

// wc.c - count words in a file
char buf[4096];

int isblank(int c) { return c == 32 || c == 9 || c == 10; }

int main(int argc, char** argv) {
    int fd; int n; int i; int words; int inword;
    if (argc < 2) exit(1);
    fd = open(argv[1], 0);
    n = read(fd, buf, 4096);
    close(fd);
    words = 0; inword = 0;
    for (i = 0; i < n; i++) {
        if (isblank(buf[i])) inword = 0;
        else if (!inword) { inword = 1; words = words + 1; }
    }
    exit(words);
    return 0;
}

04Limitations vs. standard C

What TBC does support is in the table above and the library reference. This section is only the gaps - what still isn't there vs. standard C.

Types

Preprocessor

Functions & calls

Library & runtime