# EX Script — Runtime Model

## Overview

EX Script compiles to plain JavaScript (ES2022, modules or a single bundle) and
runs on any modern JavaScript runtime: Node.js 20+, Deno, Bun, and browsers. The
runtime model is intentionally small: a tiny support library (a few hundred
lines) provides the constructs that have no direct JS equivalent. There is no
VM, no interpreter loop, no custom garbage collector.

## Compile-time vs runtime guarantees

The language is explicit about the split:

| Guarantee | Where it lives |
|---|---|
| Types, optionality, exhaustiveness, result handling | compile time (type checker) |
| `schema` validation, `or` fallback evaluation, `.require` checks, `is` narrowing, `match` dispatch | runtime (generated code + support library) |
| `Result` creation/checking | runtime (tagged objects) |

The compiler never emits runtime type checks for statically proven facts. It
*does* emit them exactly where the static type cannot prove anything: at `Any`
boundaries, at schema parsing, at `.require` and `is` tests.

## Value representation in JavaScript

| EX Script | JavaScript |
|---|---|
| `Int`, `Float` | `number` (integers checked to safe range; BigInt not needed for values ≤ 2^53) |
| `Bool` | `boolean` |
| `String`, `Char` | `string` |
| `Bytes` | `Uint8Array` |
| `Void` | `undefined` |
| `Any` | anything |
| `T?` | `T \| undefined` — absent is `undefined`, never `null` |
| `List<T>` | `T[]` |
| `Map<K, V>` | `Map` (preserves insertion order, any key type) |
| `Set<T>` | `Set` |
| tuple `(A, B)` | `[A, B]` |
| `Ok(v)` / `Err(e)` | `{ ok: true, value: v }` / `{ ok: false, error: e }` |
| enum value | frozen string constant (`Color.Red` → `"Red"`) |
| union variant `Circle(r)` | `{ tag: "Circle", r: ... }` |
| struct / schema / class | plain object / `Map` / JS `class` with `#private` fields |
| `async fun` | `async function` |
| future | `Promise` |

No proxies, no symbols-magic, no `Object.defineProperty` hacks. The generated
code is readable JavaScript that a developer can step through in any debugger.

## Result representation

A `Result<T, E>` is `{ ok: true, value }` or `{ ok: false, error }`. This is a
plain object with no prototype tricks, so results:

- survive `JSON.stringify` lossily (documented; results are not serialized),
- can be stored in collections and matched on,
- are cheap to create (two-field object literal).

`?` compiles to:

```js
const r = readFile(path);
if (!r.ok) return r;          // propagate
const text = r.value;
```

`raise "msg"` compiles to `return { ok: false, error: new ExErr("msg", stack) }`.
`ExErr` extends `Error`, so stack traces are real V8 stack traces.

## Optional representation

Optional values are `T | undefined`. Because `null` does not exist in the
language, generated code can use `=== undefined` checks exclusively.

```
x or fallback          →  x === undefined ? fallback : x
x.require("msg")       →  x === undefined ? (throw new ExErr("msg")) : x
x?.field               →  x === undefined ? undefined : x.field
if let v = x { ... }   →  if (x !== undefined) { ... }
```

JS interop: JS values that are `null` are converted to `undefined` at the
boundary (`.require`, schema validation) so that the no-null invariant holds
inside EX Script code.

## Concurrency

`async fun` compiles to `async function`; `await` compiles to `await`.

```
all(f1, f2, ...)   →  Promise.all([f1, f2, ...]) with fail-fast + cancellation
race(f1, f2, ...)  →  Promise.race([...])
timeout(ms, f)     →  Promise.race([f, delayReject(ms)])
```

Cancellation: `all` and `timeout` attach handlers that abort the remaining
futures via `AbortSignal` when a sibling fails. Futures created by `async fun`
accept an implicit cancellation signal; when cancelled, `await` points reject
with a cancellation `Err`. The details are hidden from user code — no manual
`AbortController` plumbing.

Structured concurrency (task scopes that cancel their children on exit) is
planned; `all`/`race`/`timeout` implement the observable semantics today.

## The support library (`ex-runtime`)

A small, dependency-free JS module emitted as a header of every bundle (or
imported by compiled modules). Contents:

- `Ok` / `Err` constructors and `ExErr`
- `$isResult`, `$unwrap` helpers
- schema validator builders (`$schema`)
- union dispatch helpers (`$matchVariant`)
- `all` / `race` / `timeout` concurrency primitives
- `expect` matchers for tests
- `$typeName` for diagnostics

Everything else — strings, collections, dates, HTTP, JSON, files, processes —
is the standard library written in EX Script itself, compiled like user code.

## Standard library runtime behavior

The stdlib is ordinary EX Script. `std.io`, `std.json`, `std.http`, `std.time`,
`std.string`, `std.collections`, `std.env`, `std.process`, `std.crypto`,
`std.log`, `std.cli` are modules of functions and schemas. Because they are
compiled from EX, their types are exact and their failures are `Result`s — no
`throw` anywhere in the stdlib (JS boundary functions convert).

## Resource cleanup

- Files opened with `with` are closed on scope exit, including on failure
  propagation (`?` inside a `with` body) — compiled to `try/finally`.
- Timers created by `timeout` are cleared when the race settles.
- Child processes from `std.process` are killed on scope exit if still running.

## Startup and exit

- `main` runs after all module top-levels initialize (each module initialized
  once, in dependency order; cycles are a compile error).
- `ex run` and `ex build` produce the same semantics; the bundle's `main` is
  invoked with `process.argv` minus the first two entries.
- Exit code: `0` on clean return, non-zero on unhandled failure with the error
  printed in the EX diagnostic style.