diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d1ebae96..b3f7c37f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -85,6 +85,10 @@ jobs: .build/chad build examples/hello.ts -o /tmp/hello /tmp/hello + - name: Run examples + run: bash scripts/run-examples.sh + timeout-minutes: 5 + - name: Run self-hosting tests run: node --import tsx --test tests/self-hosting.test.ts timeout-minutes: 15 @@ -188,6 +192,10 @@ jobs: .build/chad build examples/hello.ts -o /tmp/hello /tmp/hello + - name: Run examples + run: bash scripts/run-examples.sh + timeout-minutes: 5 + - name: Run self-hosting tests run: node --import tsx --test tests/self-hosting.test.ts timeout-minutes: 15 diff --git a/README.md b/README.md index 19360211..e4f5e357 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,13 @@ **[Documentation](https://cs01.github.io/ChadScript/)** · **[Benchmarks](https://cs01.github.io/ChadScript/benchmarks)** · **[GitHub Releases](https://github.com/cs01/ChadScript/releases)** -**A native compiler for TypeScript — no interpreter, no runtime, no VM.** +**As typesafe as Rust. As fast as C. As ergonomic as TypeScript.** -Your code goes through a full compilation pipeline: parse, type-check, emit LLVM IR, and link into a standalone native binary. +ChadScript is a systems programming language that uses TypeScript syntax and compiles directly to native binaries via LLVM. It is **not** a full TypeScript compiler — it's a statically-typed, natively-compiled dialect that shares TypeScript's syntax and feel while imposing stricter rules needed for native code (no generics, by-value closures, no `any`). -ChadScript is self-hosting - the compiler is written in TypeScript and compiles itself into a native binary that doesn't need any JavaScript runtime or Node.js. +Your code goes through a full compilation pipeline: parse, type-check, emit LLVM IR, and link into a standalone native binary with no Node.js, V8, or JavaScript runtime. + +ChadScript is self-hosting — the compiler (~45k lines) is written in this same dialect and compiles itself to a native binary. ## Demo diff --git a/docs/faq.md b/docs/faq.md index c16d677b..90e7c222 100644 --- a/docs/faq.md +++ b/docs/faq.md @@ -2,11 +2,15 @@ ## What is ChadScript? -ChadScript is a compiler that takes TypeScript source code and produces native ELF binaries via LLVM. It is not a runtime, interpreter, or transpiler. The output is a standalone executable with no dependencies on Node.js, V8, or any JavaScript engine. +ChadScript is a systems programming language that uses TypeScript syntax and compiles to native ELF binaries via LLVM. The goal: **as typesafe as Rust, as fast as C, as ergonomic as TypeScript**. + +It is **not** a TypeScript compiler, runtime, interpreter, or transpiler. It's a new language that shares TypeScript's syntax and feel, but imposes stricter rules required for native compilation — no `any`, no user-defined generics, by-value closures, and interfaces are data-only structs. Think of it as a TypeScript-syntax dialect for systems programming. + +The output is a standalone native binary with no dependencies on Node.js, V8, or any JavaScript engine. ## Is ChadScript a drop-in replacement for TypeScript? -No. ChadScript supports a practical subset of TypeScript. It compiles to native machine code, so all types must be known at compile time and dynamic features like `eval()` aren't available. See [Language Support](/language/features) for details. +No, and it's not trying to be. ChadScript uses TypeScript syntax as its surface language, but it's a different language with different semantics. Code that relies on runtime JS behavior — closures-by-reference, `any`, generics, `instanceof`, npm packages — won't compile. See [Language Support](/language/features) for what works. ## What TypeScript features are supported? diff --git a/docs/index.md b/docs/index.md index af004b68..ccc4d712 100644 --- a/docs/index.md +++ b/docs/index.md @@ -2,8 +2,8 @@ layout: home hero: name: ChadScript - text: TypeScript to Native Binaries - tagline: "A native compiler for TypeScript. Write TypeScript, ship a standalone binary." + text: As fast as C. As ergonomic as TypeScript. + tagline: "A natively-compiled systems language with TypeScript syntax. Write in a familiar style, ship a standalone binary with no runtime." actions: - theme: brand text: Get Started @@ -15,8 +15,8 @@ hero: features: - title: No Runtime details: Compiles to standalone ELF binaries that start in under 2ms. - - title: Familiar Syntax - details: Standard TypeScript syntax — classes, interfaces, async/await, closures. + - title: TypeScript Syntax + details: Uses TypeScript's syntax — classes, interfaces, async/await, closures. Not a full TS compiler; a natively-compiled dialect. - title: Batteries Included details: Everything you'd npm install — HTTP, SQLite, fetch, crypto, JSON — is built in. No dependencies. - title: Single-Binary Deploy diff --git a/scripts/run-examples.sh b/scripts/run-examples.sh new file mode 100755 index 00000000..6321920e --- /dev/null +++ b/scripts/run-examples.sh @@ -0,0 +1,254 @@ +#!/usr/bin/env bash +# Run all compilable examples and verify they work. +# Server examples are started, verified with curl, then killed. +# Usage: bash scripts/run-examples.sh [--compiler ] + +set -euo pipefail + +COMPILER="${COMPILER:-.build/chad}" +BUILD_DIR="/tmp/chadscript-examples" +PASSED=0 +FAILED=0 +FAILURES="" + +# Parse --compiler flag +while [[ $# -gt 0 ]]; do + case "$1" in + --compiler) COMPILER="$2"; shift 2 ;; + *) echo "Unknown option: $1"; exit 1 ;; + esac +done + +# Validate compiler exists — supports both a binary path and "node dist/chad-node.js" +COMPILER_FIRST_WORD="${COMPILER%% *}" +if [ ! -f "$COMPILER_FIRST_WORD" ] && ! command -v "$COMPILER_FIRST_WORD" &>/dev/null; then + echo "Compiler not found: $COMPILER" + echo "Build it first: node dist/chad-node.js build src/chad-native.ts -o .build/chad" + exit 1 +fi + +mkdir -p "$BUILD_DIR" + +# --- Helpers --- + +pass() { + echo " PASS: $1" + PASSED=$((PASSED + 1)) +} + +fail() { + echo " FAIL: $1 — $2" + FAILED=$((FAILED + 1)) + FAILURES="${FAILURES}\n $1: $2" +} + +compile() { + local src="$1" + local out="$2" + # Use eval-free word splitting so "node dist/chad-node.js" works + if ! $COMPILER build "$src" -o "$out" 2>&1; then + return 1 + fi + return 0 +} + +# Wait for a server to respond on a port (up to N seconds) +wait_for_server() { + local port="$1" + local max_wait="${2:-5}" + local i=0 + while [ $i -lt $max_wait ]; do + if curl -s -o /dev/null -w '' "http://localhost:$port/" 2>/dev/null; then + return 0 + fi + sleep 1 + i=$((i + 1)) + done + return 1 +} + +echo "=== ChadScript Examples Runner ===" +echo "Compiler: $COMPILER" +echo "Build dir: $BUILD_DIR" +echo "" + +# --- 1. hello.ts (simple print) --- + +echo "[1/8] hello.ts" +if compile examples/hello.ts "$BUILD_DIR/hello"; then + OUTPUT=$("$BUILD_DIR/hello" 2>&1) || true + if echo "$OUTPUT" | grep -q "Hello from ChadScript"; then + pass "hello.ts" + else + fail "hello.ts" "unexpected output: $OUTPUT" + fi +else + fail "hello.ts" "compile failed" +fi + +# --- 2. timers.ts (event loop, self-terminating) --- + +echo "[2/8] timers.ts" +if compile examples/timers.ts "$BUILD_DIR/timers"; then + # No `timeout` on macOS — use background process + wait with a deadline + "$BUILD_DIR/timers" > "$BUILD_DIR/timers.out" 2>&1 & + TIMER_PID=$! + ( sleep 10; kill "$TIMER_PID" 2>/dev/null ) & + WATCHDOG_PID=$! + wait "$TIMER_PID" 2>/dev/null || true + kill "$WATCHDOG_PID" 2>/dev/null || true + wait "$WATCHDOG_PID" 2>/dev/null || true + OUTPUT=$(cat "$BUILD_DIR/timers.out") + if echo "$OUTPUT" | grep -q "tick 3"; then + pass "timers.ts" + else + fail "timers.ts" "didn't see tick 3: $OUTPUT" + fi +else + fail "timers.ts" "compile failed" +fi + +# --- 3. cli-parser-demo.ts (argparse) --- + +echo "[3/8] cli-parser-demo.ts" +if compile examples/cli-parser-demo.ts "$BUILD_DIR/cli-parser-demo"; then + OUTPUT=$("$BUILD_DIR/cli-parser-demo" -v -o result.txt myfile.txt 2>&1) || true + if echo "$OUTPUT" | grep -q "verbose"; then + pass "cli-parser-demo.ts" + else + fail "cli-parser-demo.ts" "unexpected output: $OUTPUT" + fi +else + fail "cli-parser-demo.ts" "compile failed" +fi + +# --- 4. query.ts (sqlite in-memory) --- + +echo "[4/8] query.ts" +if compile examples/query.ts "$BUILD_DIR/query"; then + OUTPUT=$("$BUILD_DIR/query" 2>&1) || true + if echo "$OUTPUT" | grep -q "Found"; then + pass "query.ts" + else + fail "query.ts" "unexpected output: $OUTPUT" + fi +else + fail "query.ts" "compile failed" +fi + +# --- 5. word-count.ts (file I/O) --- + +echo "[5/8] word-count.ts" +if compile examples/word-count.ts "$BUILD_DIR/word-count"; then + # Create a test file to count + echo "hello world foo bar" > "$BUILD_DIR/test-input.txt" + OUTPUT=$("$BUILD_DIR/word-count" "$BUILD_DIR/test-input.txt" 2>&1) || true + if echo "$OUTPUT" | grep -q "words"; then + pass "word-count.ts" + else + fail "word-count.ts" "unexpected output: $OUTPUT" + fi +else + fail "word-count.ts" "compile failed" +fi + +# --- 6. string-search.ts (grep-like) --- + +echo "[6/8] string-search.ts" +if compile examples/string-search.ts "$BUILD_DIR/string-search"; then + # Create a test file to search + printf "line one\nfind me here\nline three\n" > "$BUILD_DIR/search-input.txt" + OUTPUT=$("$BUILD_DIR/string-search" "find" "$BUILD_DIR/search-input.txt" 2>&1) || true + if echo "$OUTPUT" | grep -q "find"; then + pass "string-search.ts" + else + fail "string-search.ts" "unexpected output: $OUTPUT" + fi +else + fail "string-search.ts" "compile failed" +fi + +# --- 7. http-server.ts (server + curl) --- + +echo "[7/8] http-server.ts" +if compile examples/http-server.ts "$BUILD_DIR/http-server"; then + PORT=18080 + "$BUILD_DIR/http-server" -p "$PORT" & + SERVER_PID=$! + + if wait_for_server "$PORT" 5; then + # Test root endpoint + RESP=$(curl -s "http://localhost:$PORT/") + if echo "$RESP" | grep -q "ChadScript"; then + # Test JSON endpoint + RESP2=$(curl -s "http://localhost:$PORT/json") + if echo "$RESP2" | grep -q "message"; then + # Test POST echo + RESP3=$(curl -s -X POST -d 'hello world' "http://localhost:$PORT/echo") + if echo "$RESP3" | grep -q "hello world"; then + pass "http-server.ts" + else + fail "http-server.ts" "POST /echo failed: $RESP3" + fi + else + fail "http-server.ts" "GET /json failed: $RESP2" + fi + else + fail "http-server.ts" "GET / failed: $RESP" + fi + else + fail "http-server.ts" "server didn't start on port $PORT" + fi + + kill "$SERVER_PID" 2>/dev/null || true + wait "$SERVER_PID" 2>/dev/null || true +else + fail "http-server.ts" "compile failed" +fi + +# --- 8. hackernews/app.ts (full-stack server + curl) --- + +echo "[8/8] hackernews/app.ts" +if compile examples/hackernews/app.ts "$BUILD_DIR/hackernews"; then + PORT=18081 + "$BUILD_DIR/hackernews" -p "$PORT" & + SERVER_PID=$! + + if wait_for_server "$PORT" 5; then + # Test API endpoint + RESP=$(curl -s "http://localhost:$PORT/api/posts") + if echo "$RESP" | grep -q "ChadScript"; then + # Test HTML page + RESP2=$(curl -s "http://localhost:$PORT/") + if echo "$RESP2" | grep -q "html"; then + pass "hackernews/app.ts" + else + fail "hackernews/app.ts" "GET / didn't return HTML: ${RESP2:0:100}" + fi + else + fail "hackernews/app.ts" "GET /api/posts failed: ${RESP:0:100}" + fi + else + fail "hackernews/app.ts" "server didn't start on port $PORT" + fi + + kill "$SERVER_PID" 2>/dev/null || true + wait "$SERVER_PID" 2>/dev/null || true +else + fail "hackernews/app.ts" "compile failed" +fi + +# --- Summary --- + +echo "" +echo "=== Results ===" +echo "Passed: $PASSED / $((PASSED + FAILED))" +echo "Failed: $FAILED / $((PASSED + FAILED))" + +if [ $FAILED -gt 0 ]; then + echo -e "\nFailures:$FAILURES" + exit 1 +fi + +echo "" +echo "All examples passed!"