Documentation

Install

Homebrew (recommended)

brew install nathanjmorton/zigtsc/zigtsc
click to copy

Upgrade:

brew update && brew upgrade zigtsc
click to copy
or
zigtsc upgrade
click to copy

Shell script

curl -fsSL https://raw.githubusercontent.com/nathanjmorton/zigtsc/main/install.sh | bash
click 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=ReleaseFast
click to copy

Requires Zig 0.16.0.

Command reference

zigtsc init <directory>Scaffold a new project with a starter main.ts
zigtsc 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/wasm
zigtsc run <binary-or-wasm>Run a native binary or .wasm module (via wasmtime)
zigtsc versionPrint the installed version
zigtsc upgradeUpdate to the latest release

Full 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.wasm
click 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, literals
lexer.zigTokenizer with comment skipping, string/number/identifier support
ast.zigAST node definitions — class_decl, method_decl, constructor_decl, new_expr, this_expr
parser.zigRecursive descent parser with class/method/constructor/new/this support
checker.zigType checker — scoped symbols, ClassDef, class_t type, this binding
codegen.zigC emitter — interface→struct, console.log→printf (single-target -target c)
codegen_js.zigJS emitter — strips types, classes→ES6 classes, interfaces omitted
codegen_cpp.zigC++ emitter — unified .h/.c/.cpp with bridge-mode, also per-class -target cpp
main.zigCLI entry point — init / transpile / compile / run / upgrade commands

Supported types

number→ double
boolean→ bool
string→ const char*
void→ void
i32→ int32_t
i64→ int64_t
f32→ float
f64→ double
T[]→ double* (simplified)
interface Foo { ... }→ typedef struct / C++ struct
class Foo { ... }→ C++ class with .h/.cpp pair

Supported 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:

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 all
click to copy

22 tests covering the lexer, parser, checker, C codegen, JS codegen, and C++ codegen. Zero memory leaks.