Quickstart
From a fresh clone to a running native binary. With bun and LLVM already installed, every command on this page (clone, setup, first build including the runtime, a rejection) finishes in seconds. Nothing is released yet (2.0.0-alpha.1 is in preparation, see the changelog). ChadScript builds from a source checkout; there is no prebuilt binary yet.
1. Install the prerequisites
You need bun 1.3 (it runs the compiler), LLVM's clang and opt, and Node.js 20.6 or newer (for the test suite). ChadScript is tested on macOS arm64 and Linux x86-64.
curl -fsSL https://bun.sh/install | bash
brew install llvm # then put "$(brew --prefix llvm)/bin" on your PATHcurl -fsSL https://bun.sh/install | bash
sudo apt-get install -y clang-18 llvm-18
export CHAD_CLANG=clang-18 CHAD_OPT=opt-182. Clone and set up
git clone https://github.com/cs01/ChadScript.git
cd ChadScript
bun install
sh scripts/setup-milo.sh # fetches the pinned Milo compiler that builds the runtime
bin/chad doctorThe last command checks everything in one go, then compiles and runs a hello world to prove the whole path works. Expect this (versions will differ):
ok bun 1.3.10
ok node v25.3.0
ok clang 22.1.8 (clang)
ok opt 22.1.8 (opt)
ok milo pinned ba9484985954
ok hello world compiled and ran
all checks passedA failed check prints the fix on the next line. Node is only needed for the test suite; building programs does not use it.
3. Compile a program
Save this as hello.ts in the repository root. It is ordinary TypeScript: an interface, an array of objects, a Map, a template string.
interface Order {
customer: string;
total: number;
}
const orders: Order[] = [
{ customer: "ada", total: 120 },
{ customer: "lin", total: 80 },
{ customer: "ada", total: 45.5 },
];
const byCustomer = new Map<string, number>();
for (const o of orders) {
byCustomer.set(o.customer, (byCustomer.get(o.customer) ?? 0) + o.total);
}
for (const name of byCustomer.keys()) {
console.log(`${name.padEnd(5)}${byCustomer.get(name)}`);
}bin/chad run hello.tsada 165.5
lin 80The first build also compiles the runtime and caches it; later builds reuse it. run builds into a temp directory and runs the result. build writes the binary where you say:
bin/chad build hello.ts -o hello
./hello
ls -lh helloThe binary is self-contained: on macOS, otool -L hello lists only the system C library. The same file runs under Node unchanged (node hello.ts on Node 23.6 or later) and prints the same bytes.
4. See a rejection
Now a program outside the subset: it iterates a Map directly and uses loose equality. Save it as rejected.ts:
// @expect-reject: CS1222
const stock = new Map<string, number>();
stock.set("apples", 3);
stock.set("pears", 0);
for (const [fruit, count] of stock) {
if (count == 0) console.log(`${fruit}: sold out`);
}bin/chad check rejected.ts$ bin/chad check rejected.ts
error[CS1222]: iterating a Map directly with `for...of` is not supported yet
--> rejected.ts:6:1
help: iterate its keys and read each value: `for (const k of m.keys())`
error[CS1203]: `==` is not supported
--> rejected.ts:7:7
help: use `===`
2 error(s)check runs only the type checker and the subset checks, so it is the fast way to ask "is this in the subset?". Each diagnostic has a code (explained in the error reference), a location, and a rewrite that stays inside the subset. Applying both hints:
// The rewrite of rejected.ts that the diagnostics suggest.
const stock = new Map<string, number>();
stock.set("apples", 3);
stock.set("pears", 0);
for (const fruit of stock.keys()) {
const count = stock.get(fruit) ?? 0;
if (count === 0) console.log(`${fruit}: sold out`);
}pears: sold outNext
- Build a multi-file project, or a CLI tool.
- Language reference: what the subset covers, one topic per page.
- Is ChadScript for you?: what fits today and what does not.