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.
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.
| Area | Status | Notes |
|---|---|---|
Types int, char | yes | 32-bit int; 1-byte char (read unsigned) |
Pointers, &x *p | yes | any depth; correct pointer arithmetic on declared pointers/arrays |
| Arrays | yes | a[i] and multi-dimensional a[i][j]; brace initializers incl. unsized int a[] = {1,2,3} and char s[] = "hi" |
| Functions, recursion | yes | args passed in registers; prototypes / forward declarations OK |
| Function pointers | yes | int (*fp)(int,int) vars, globals, callback params; untyped (address only) |
| Control flow | yes | if/else while for do/while switch/case break continue goto return |
| Operators | yes | + - * / % & | ^ << >> == != < <= > >= && || ! ~ ?: ,, ++ --, += -= *= /= %= &= |= ^= |
sizeof | yes | sizeof(int), sizeof(char*), and sizeof expr |
enum, _Bool | yes | named constants; _Bool aliases int |
static | yes | file-scope statics and static locals (value kept across calls) |
typedef | yes | aliases for scalar, pointer, array, and function-pointer types |
| Preprocessor | yes | object & function-like #define, #undef, conditionals #if/#ifdef/#ifndef/#elif/#else/#endif with defined() and constant expressions |
| Headers, multi-file | yes | split a program across .h files with textual #include "file.h" (resolved relative to the including file; #ifndef guards work) - one translation unit, no linker |
| Literals | yes | decimal, 0x hex, 0b binary, 0 octal, U/L suffixes, full char/string escapes |
| Comments | yes | // and /* */ |
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.
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;
}
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.
short, long, long long, float, double, or the signed keyword. The only types are int, unsigned int, char, and unsigned char.struct / union - the big structural gap. Data structures are built from parallel arrays.const / volatile / register / extern are accepted but ignored - no const-correctness enforcement.char is unsigned - a 0-255 byte, so c < 0 is never true and there is no signed char.#/## stringize/paste, no nested macro re-expansion, and no #include <...> system headers....) - no printf-style; roll your own.r1-r6.printf, no stdio.h, no math.h; only the built-in prelude and __sys.__sys.