Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,10 @@ jobs:
exit 1
fi

- name: Build tree-sitter TypeScript objects
- name: Build tree-sitter TSX objects
run: |
mkdir -p build
TS_SRC=node_modules/tree-sitter-typescript/typescript/src
TS_SRC=node_modules/tree-sitter-typescript/tsx/src
TS_INCLUDE=node_modules/tree-sitter-typescript
clang -c -O2 -fPIC -I $TS_SRC -I $TS_INCLUDE $TS_SRC/parser.c -o build/tree-sitter-typescript-parser.o
clang -c -O2 -fPIC -I $TS_SRC -I $TS_INCLUDE $TS_SRC/scanner.c -o build/tree-sitter-typescript-scanner.o
Expand Down Expand Up @@ -158,10 +158,10 @@ jobs:
exit 1
fi

- name: Build tree-sitter TypeScript objects
- name: Build tree-sitter TSX objects
run: |
mkdir -p build
TS_SRC=node_modules/tree-sitter-typescript/typescript/src
TS_SRC=node_modules/tree-sitter-typescript/tsx/src
TS_INCLUDE=node_modules/tree-sitter-typescript
clang -c -O2 -fPIC -I $TS_SRC -I $TS_INCLUDE $TS_SRC/parser.c -o build/tree-sitter-typescript-parser.o
clang -c -O2 -fPIC -I $TS_SRC -I $TS_INCLUDE $TS_SRC/scanner.c -o build/tree-sitter-typescript-scanner.o
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/cross-compile.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ jobs:
- name: Build tree-sitter objects
run: |
mkdir -p build
TS_SRC=node_modules/tree-sitter-typescript/typescript/src
TS_SRC=node_modules/tree-sitter-typescript/tsx/src
TS_INCLUDE=node_modules/tree-sitter-typescript
clang -c -O2 -fPIC -I $TS_SRC -I $TS_INCLUDE $TS_SRC/parser.c -o build/tree-sitter-typescript-parser.o
clang -c -O2 -fPIC -I $TS_SRC -I $TS_INCLUDE $TS_SRC/scanner.c -o build/tree-sitter-typescript-scanner.o
Expand Down
4 changes: 2 additions & 2 deletions c_bridges/treesitter-bridge.c
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
#include <stdint.h>
#include <stdbool.h>

extern TSLanguage *tree_sitter_typescript(void);
extern TSLanguage *tree_sitter_tsx(void);
extern void *GC_malloc_uncollectable(size_t size);
extern void *GC_malloc_atomic(size_t size);

TSTree *__ts_parse_source(const char *source, uint32_t length) {
TSParser *parser = ts_parser_new();
TSLanguage *lang = tree_sitter_typescript();
TSLanguage *lang = tree_sitter_tsx();
ts_parser_set_language(parser, lang);
return ts_parser_parse_string(parser, NULL, source, length);
}
Expand Down
1 change: 1 addition & 0 deletions docs/getting-started/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ SDKs are installed to `~/.chadscript/targets/<name>/`.
| `--emit-llvm`, `-S` | Output LLVM IR only (no binary) |
| `--keep-temps` | Keep intermediate files (`.ll`, `.o`) |
| `-fsanitize=address` | Build with AddressSanitizer (ASAN) |
| `--link-obj <path>` | Link an external object file or static library (repeatable) |

## Cross-Compilation

Expand Down
56 changes: 56 additions & 0 deletions docs/language/limitations.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ ChadScript supports a practical subset of TypeScript. All types must be known at
| Default parameters | Supported |
| Rest parameters (`...args`) | Supported |
| Closures | Supported (capture by value, not by reference) |
| `declare function` (FFI) | Supported (see [FFI](#foreign-function-interface-ffi)) |
| Async generators / `for await...of` | Not supported |

## Types and Data Structures
Expand Down Expand Up @@ -111,6 +112,61 @@ ChadScript supports a practical subset of TypeScript. All types must be known at
| `.then()`, `.catch()`, `.finally()` | Supported |
| `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval` | Supported |

## JSX

| Feature | Status |
|---------|--------|
| JSX elements (`<Tag prop={v} />`) | Supported (desugared to `createElement()` calls) |
| Fragments (`<>...</>`) | Supported |
| Expression attributes (`prop={expr}`) | Supported |
| String attributes (`prop="text"`) | Supported |
| Self-closing elements (`<Tag />`) | Supported |
| Nested elements | Supported |

JSX is desugared at parse time into `createElement(tag, props, children)` calls. You provide the `createElement` function — ChadScript doesn't ship a framework. Files must use `.tsx` extension.

```tsx
interface Props {
text: string;
color: number;
}

function createElement(tag: string, props: Props, children: string[]): string {
// your rendering logic here
return tag;
}

const ui = <Label text="hello" color={0xff0000} />;
// desugars to: createElement("Label", { text: "hello", color: 0xff0000 }, [])
```

## Foreign Function Interface (FFI)

`declare function` lets you call external C functions with zero-cost type mappings:

```ts
declare function zr_init(): i8_ptr;
declare function zr_draw_text(engine: i8_ptr, x: i32, y: i32, text: i8_ptr, fg: u32, bg: u32): void;
```

FFI type aliases map directly to LLVM types with no double conversion:

| Type Alias | LLVM Type | Description |
|-----------|-----------|-------------|
| `i8`, `i16`, `i32`, `i64` | `i8`, `i16`, `i32`, `i64` | Signed integers |
| `u8`, `u16`, `u32`, `u64` | `i8`, `i16`, `i32`, `i64` | Unsigned integers (same LLVM type) |
| `f32` | `float` | 32-bit float |
| `f64` | `double` | 64-bit float |
| `i8_ptr`, `ptr` | `i8*` | Opaque pointer |

Link external object files with `--link-obj`:

```bash
chad build app.ts -o app --link-obj bridge.o --link-obj /path/to/libfoo.a
```

Linker flags (`-lm`, `-lpthread`, etc.) are auto-detected from linked libraries.

## Dynamic Features

These require runtime code evaluation and are not possible in a native compiler:
Expand Down
14 changes: 14 additions & 0 deletions docs/language/type-mappings.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ Node.js APIs map to native equivalents, inlined directly as LLVM IR at the call
| `crypto.sha256()` etc. | OpenSSL EVP API (`libcrypto`) |
| `sqlite.open()` etc. | `libsqlite3` |

## FFI Types (`declare function`)

These type aliases are used in `declare function` signatures for zero-cost C interop. They map directly to LLVM integer/float types — no double conversion overhead.

| Type Alias | LLVM IR | Notes |
|---|---|---|
| `i8`, `i16`, `i32`, `i64` | `i8`, `i16`, `i32`, `i64` | Signed integers |
| `u8`, `u16`, `u32`, `u64` | `i8`, `i16`, `i32`, `i64` | Unsigned (same LLVM representation) |
| `f32` | `float` | 32-bit float |
| `f64` | `double` | 64-bit float (same as `number`) |
| `i8_ptr`, `ptr` | `i8*` | Opaque pointer (same as `string`) |

In regular ChadScript code, all numbers are `double`. FFI types are only meaningful in `declare function` parameter/return types — they tell the compiler to emit native-width arguments instead of converting through `double`.

## Key Differences from TypeScript

- **No `any` or `unknown`** — every value has a concrete type at compile time
Expand Down
40 changes: 40 additions & 0 deletions examples/tui/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# TUI Demo (Zireael)

Interactive terminal UI app compiled to a native binary with ChadScript.

Uses [Zireael](https://github.com/nicegraf/Zireael) — a C11 terminal rendering engine with a binary drawlist protocol.

## Prerequisites

- Zireael cloned and built (set `ZIREAEL_DIR` env var to point to it)
- CMake + clang

## Build & Run

```bash
ZIREAEL_DIR=../Zireael bash examples/tui/build.sh
.build/examples/tui/app
```

## Controls

- **UP/DOWN** — increment/decrement counter by 1
- **LEFT/RIGHT** — increment/decrement counter by 10
- **ESC** — quit

## How It Works

```
TypeScript (app.tsx)
│ declare function zr_*() — typed FFI declarations
C bridge (zireael-bridge.c)
│ builds drawlist byte buffers, parses event batches
libzireael.a
│ diffs framebuffer, flushes terminal escape codes
Terminal
```

The `declare function` syntax tells ChadScript to emit correct LLVM IR calls to external C functions. The `--link-obj` flag links the bridge and Zireael library into the final binary.
106 changes: 106 additions & 0 deletions examples/tui/app-jsx.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// ChadScript JSX TUI Demo — declarative version of the Zireael counter app
//
// Same interactive counter as app.tsx, but uses JSX syntax for the draw calls.
// JSX desugars <Tag prop={v} /> to createElement("Tag", {prop: v}, []).
// Our createElement draws directly to the terminal as a side effect.
//
// Build: bash examples/tui/build.sh
// Run: .build/examples/tui/app-jsx

// FFI: zireael-bridge.c — engine lifecycle
declare function zr_init(): i8_ptr;
declare function zr_destroy(engine: i8_ptr): void;

// FFI: zireael-bridge.c — event polling (returns "key:escape", "key:up", etc.)
declare function zr_poll(engine: i8_ptr, timeout_ms: i32): i8_ptr;

// FFI: zireael-bridge.c — drawlist construction
declare function zr_begin(engine: i8_ptr): void;
declare function zr_clear(engine: i8_ptr): void;
declare function zr_fill_rect(
engine: i8_ptr,
x: i32,
y: i32,
w: i32,
h: i32,
fg: u32,
bg: u32,
): void;
declare function zr_draw_text(engine: i8_ptr, x: i32, y: i32, text: i8_ptr, fg: u32, bg: u32): void;
declare function zr_present(engine: i8_ptr): f64;

// Colors (0x00RRGGBB)
const WHITE = 0xffffff;
const CYAN = 0x00ffff;
const GRAY = 0x888888;
const DARK_BLUE = 0x002244;
const BLACK = 0x000000;
const GREEN = 0x00ff88;

// Props for all JSX elements — every element receives the full set.
// Box uses x/y/w/h/fg/bg for fill_rect; Text uses x/y/text/fg/bg for draw_text.
interface Props {
x: number;
y: number;
w: number;
h: number;
fg: number;
bg: number;
text: string;
}

// Module-level engine so createElement can access it
const engine = zr_init();

// Side-effect createElement: draws to the terminal immediately via Zireael FFI.
// JSX evaluates children before parents (inner-to-outer), so we use flat fragments
// (<>...</>) instead of nesting — this gives us left-to-right draw order, ensuring
// backgrounds (Box) render before foreground text (Text).
function createElement(tag: string, props: Props, children: string[]): string {
if (tag === "Box") {
zr_fill_rect(engine, props.x, props.y, props.w, props.h, props.fg, props.bg);
} else if (tag === "Text") {
zr_draw_text(engine, props.x, props.y, props.text, props.fg, props.bg);
}
// "Fragment" (from <>) does nothing — just groups siblings for ordering
return tag;
}

let count = 0;
let running = true;

while (running) {
const event = zr_poll(engine, 16);

if (event === "key:escape") {
running = false;
} else if (event === "key:up") {
count = count + 1;
} else if (event === "key:down") {
count = count - 1;
} else if (event === "key:right") {
count = count + 10;
} else if (event === "key:left") {
count = count - 10;
}

zr_begin(engine);
zr_clear(engine);

// JSX renders via side effects — each element triggers zr_fill_rect or zr_draw_text.
// Fragment (<>) ensures left-to-right evaluation: Box backgrounds draw first, then Text.
const ui = (
<>
<Box x={0} y={0} w={50} h={1} fg={WHITE} bg={DARK_BLUE} text="" />
<Text x={2} y={0} w={0} h={0} fg={CYAN} bg={DARK_BLUE} text="ChadScript JSX TUI" />
<Text x={2} y={2} w={0} h={0} fg={GREEN} bg={BLACK} text={"Counter: " + count.toString()} />
<Text x={2} y={4} w={0} h={0} fg={GRAY} bg={BLACK} text="UP/DOWN +/- 1" />
<Text x={2} y={5} w={0} h={0} fg={GRAY} bg={BLACK} text="LEFT/RIGHT +/- 10" />
<Text x={2} y={6} w={0} h={0} fg={GRAY} bg={BLACK} text="ESC quit" />
</>
);

zr_present(engine);
}

zr_destroy(engine);
78 changes: 78 additions & 0 deletions examples/tui/app.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
// ChadScript TUI Demo — Zireael-powered native terminal app
//
// A simple interactive counter demonstrating the full render loop:
// poll events → update state → build drawlist → present
//
// Build: bash examples/tui/build.sh
// Run: .build/examples/tui/app

// FFI: zireael-bridge.c — engine lifecycle
// i8_ptr = opaque C pointer, i32/u32 = zero-cost integer types (no double conversion)
declare function zr_init(): i8_ptr;
declare function zr_destroy(engine: i8_ptr): void;

// FFI: zireael-bridge.c — event polling (returns "key:escape", "key:up", etc.)
declare function zr_poll(engine: i8_ptr, timeout_ms: i32): i8_ptr;

// FFI: zireael-bridge.c — drawlist construction
declare function zr_begin(engine: i8_ptr): void;
declare function zr_clear(engine: i8_ptr): void;
declare function zr_fill_rect(
engine: i8_ptr,
x: i32,
y: i32,
w: i32,
h: i32,
fg: u32,
bg: u32,
): void;
declare function zr_draw_text(engine: i8_ptr, x: i32, y: i32, text: i8_ptr, fg: u32, bg: u32): void;
declare function zr_present(engine: i8_ptr): f64;

// Colors (0x00RRGGBB)
const WHITE = 0xffffff;
const CYAN = 0x00ffff;
const GRAY = 0x888888;
const DARK_BLUE = 0x002244;
const BLACK = 0x000000;
const GREEN = 0x00ff88;
const YELLOW = 0xffcc00;

const engine = zr_init();
let count = 0;
let running = true;

while (running) {
const event = zr_poll(engine, 16);
// console.log(event);
if (event === "key:escape") {
running = false;
} else if (event === "key:up") {
count = count + 1;
} else if (event === "key:down") {
count = count - 1;
} else if (event === "key:right") {
count = count + 10;
} else if (event === "key:left") {
count = count - 10;
}

zr_begin(engine);
zr_clear(engine);

// Header bar
zr_fill_rect(engine, 0, 0, 50, 1, WHITE, DARK_BLUE);
zr_draw_text(engine, 2, 0, "ChadScript TUI Demo", CYAN, DARK_BLUE);

// Counter display
zr_draw_text(engine, 2, 2, "Counter: " + count.toString(), GREEN, GREEN);

// Instructions
zr_draw_text(engine, 2, 4, "UP/DOWN +/- 1", GRAY, BLACK);
zr_draw_text(engine, 2, 5, "LEFT/RIGHT +/- 10", GRAY, BLACK);
zr_draw_text(engine, 2, 6, "ESC quit", GRAY, BLACK);

zr_present(engine);
}

zr_destroy(engine);
Loading
Loading