Documentation
Install
Homebrew (recommended)
brew install nathanjmorton/zigtsc/zigtscclick to copy
Upgrade:
brew update && brew upgrade zigtscclick to copy
zigtsc upgradeclick to copy
Shell script
curl -fsSL https://raw.githubusercontent.com/nathanjmorton/zigtsc/main/install.sh | bashclick to copy
Detects your platform (macOS/Linux, arm64/x86_64), downloads the binary to ~/.zigtsc/bin/zigtsc, and updates your PATH. Upgrade with zigtsc upgrade or re-run the install script.
Build from source
git clone https://github.com/nathanjmorton/zigtsc && \ cd zigtsc && \ zig build -Doptimize=ReleaseFastclick to copy
Requires Zig 0.16.0.
Command reference
zigtsc init <directory>Scaffold a new project with a starter main.tszigtsc transpile <input.ts>Transpile TypeScript → .h .c .cpp .js in src/zigtscout/zigtsc compile <zigtscout-dir>Compile C/C++ sources → zig-out/bin + zig-out/wasmzigtsc run <binary-or-wasm>Run a native binary or .wasm module (via wasmtime)zigtsc versionPrint the installed versionzigtsc upgradeUpdate to the latest releaseFull pipeline
Scaffold a project, transpile TypeScript to C/C++/JS, compile to a native binary and Wasm module, then run both.
zigtsc init demo && \ zigtsc transpile demo/src/main.ts && \ zigtsc compile demo/src/zigtscout && \ zigtsc run demo/zig-out/bin/main && zigtsc run demo/zig-out/wasm/main.wasmclick to copy
What each step produces:
zigtsc init demo → demo/src/main.ts zigtsc transpile → demo/src/zigtscout/main.h .c .cpp .js zigtsc compile → demo/zig-out/bin/main demo/zig-out/wasm/main.wasm zigtsc run → executes the binary or wasm module
Compiler pipeline
source.ts → Lexer → Tokens → Parser → AST → Checker →┬→ CodeGen → output.c (single-target)
├→ CodeGenJS → output.js (single-target)
└→ CodeGenCpp → .h + .c + .cpp + .js (unified)Each stage is a separate Zig source file. Parser and checker are shared across all targets.
token.zigToken definitions — keywords (including class/new/this), operators, literalslexer.zigTokenizer with comment skipping, string/number/identifier supportast.zigAST node definitions — class_decl, method_decl, constructor_decl, new_expr, this_exprparser.zigRecursive descent parser with class/method/constructor/new/this supportchecker.zigType checker — scoped symbols, ClassDef, class_t type, this bindingcodegen.zigC emitter — interface→struct, console.log→printf (single-target -target c)codegen_js.zigJS emitter — strips types, classes→ES6 classes, interfaces omittedcodegen_cpp.zigC++ emitter — unified .h/.c/.cpp with bridge-mode, also per-class -target cppmain.zigCLI entry point — init / transpile / compile / run / upgrade commandsSupported types
number→ doubleboolean→ boolstring→ const char*void→ voidi32→ int32_ti64→ int64_tf32→ floatf64→ doubleT[]→ double* (simplified)interface Foo { ... }→ typedef struct / C++ structclass Foo { ... }→ C++ class with .h/.cpp pairSupported syntax
Variables
let x: number = 42; → double x = 42; (C) const msg: string = "hi"; → const msg = "hi"; (JS)
Functions
function add(a: number, b: number): number {
return a + b;
}
→ C: double add(double a, double b) { return (a + b); }
→ JS: function add(a, b) { return (a + b); }
→ C++: double add(double a, double b) { return (a + b); } (in main.cpp)Interfaces
interface Point { x: number; y: number; }
→ C: typedef struct { double x; double y; } Point;
→ JS: (omitted — compile-time only)
→ C++: struct Point { double x; double y; };Classes (Go-style, no inheritance)
class Counter {
value: i32;
constructor(init: i32) { this.value = init; }
increment(): void { this.value = this.value + 1; }
getVal(): i32 { return this.value; }
}
const c = new Counter(10);
→ JS: class Counter { constructor(init) { ... } ... }
→ C++: Counter.h + Counter.cpp (separate files)
Counter::Counter(int32_t init) { this->value = init; }Control flow
if / else if / else, while, for (C-style 3-part), return — all map directly to their target equivalents.
Expressions
Arithmetic (+ - * / %), comparison (< > <= >= == != === !==), logical (&& || !), assignment (= += -= *= /=), function calls, member access (a.b / this.x), index access (a[i]), new ClassName(args).
console.log
In C/C++ targets: transpiled to printf() with format strings inferred from types. In JS target: stays as console.log().
Explicitly excluded
These TypeScript features are intentionally unsupported:
- Inheritance /
extends - Static fields/methods
- Closures / capturing nested functions
async/await, Promises- Generics, union types,
any,unknown - Decorators, destructuring, spread, optional chaining
- Getters/setters, access modifiers
eval,new Function()
Memory model
Stack allocation by default for scalars and small structs. Arrays are heap-allocated with malloc — the caller is responsible for free. There is no garbage collector. The C/C++ targets are explicitly manual-memory. The JS target inherits the JS runtime's GC.
What zigtsc init generates
zigtsc init creates a main.ts that exercises every language feature. The scaffold includes an interface (Point), a class (Counter) with constructor and methods, a free function (distance), and top-level code that instantiates the class with new.
interface Point { x: number; y: number; }
function distance(a: Point, b: Point): number {
let dx: number = b.x - a.x;
let dy: number = b.y - a.y;
return dx * dx + dy * dy;
}
class Counter {
value: i32;
constructor(init: i32) { this.value = init; }
increment(): void { this.value = this.value + 1; }
decrement(): void { this.value = this.value - 1; }
getVal(): i32 { return this.value; }
}
const p1: Point = { x: 0, y: 0 };
const p2: Point = { x: 3, y: 4 };
console.log(distance(p1, p2));
const c = new Counter(10);
c.increment();
c.increment();
c.decrement();
console.log(c.getVal());Running tests
zig build test --summary allclick to copy
22 tests covering the lexer, parser, checker, C codegen, JS codegen, and C++ codegen. Zero memory leaks.