diff --git a/README.md b/README.md index 3f30318..c4ff3cd 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,152 @@ # ReactiveScript -Codename for a language that carries Solid 2.0's reactive semantics as language -semantics: equations by default, writes confined to imperative scope, ownership -and async (pending, holds, lanes) as things the compiler checks rather than the -runtime warns about. +A small language for reactive UIs that carries +[Solid](https://github.com/solidjs/solid) 2.0's reactive semantics as +*language* semantics, and checks them before your code runs. Files are `.rx`; +they compile to plain Solid 2.0. > **Status:** early design work. The spec, its scenarios and the reference > implementation change often, and nothing here is released. See > [CONTRIBUTING.md](CONTRIBUTING.md) to run the suite or propose a change. -This repository is the **spec** and its **reference implementation**. The -production compiler is a separate Rust crate over Solid's own -`solidjs-compiler` (`spec/11-compiler.md`): ReactiveScript parses, checks -and lowers to an Oxc AST; Solid's compiler lowers the JSX. `ref/` is its -conformance suite, run on Solid's `next` branch built from source. +## Why -## Layout +In Solid you already write UIs as derivations over signals. Most of what goes +wrong is a *shape* mistake: writing to something that should be derived, +writing during a computation, reading async data with no loading or error +boundary above it, leaking a value past the owner that created it. Solid's +dev runtime warns about many of these when they happen. ReactiveScript makes +them things the compiler refuses, the way Rust refuses a borrow, so the +program that runs is one where those mistakes cannot happen +([spec/08](spec/08-constraints.md), [spec/99](spec/99-diagnostics.md)). + +The checks are static only. A `.rx` program compiles to ordinary Solid 2.0 +code and adds nothing at run time beyond what Solid does +([spec/11](spec/11-compiler.md)). + +It is also meant to be easy for an AI to write correctly. The rules are +small and explicit, and each checker error names the rule it enforces and usually the fix. We +test that directly: fresh agents given only the spec wrote the apps in +[`examples/ai`](examples/ai) and [`examples/ai2`](examples/ai2) cold and +predicted 70 of 71 frames of their behaviour before running them +([examples/README.md](examples/README.md)). + +## A taste + +Three kinds of names do all the work: + +- `source x := 0` is state. Only a source can be written, with `:=`. +- `y = x * 2` is an **equation**: it is always true, and recomputes when + what it reads changes (E-1). +- Writes happen only in imperative scope: an event handler or an `action` + (W-1, W-2). + +```rx +export default component Counter() { + source count := 0 + double = count * 2 + + +} +``` + +Write to the equation instead, `double := 4`, and the checker refuses it: + +``` +counter.rx:5: error WRITE_TO_NON_SOURCE [W-1] double is an equation: only sources are written (W-1) +``` + +Async is an `await` in an equation. The compiler tracks that the value can be +pending and can fail, so it asks you to say what to show in both cases: + +```rx +import pure { fetchUser } from "./api" // `pure`: safe to call from an equation (R-io-4) + +export default component Profile() { + source id := 1 + user = await fetchUser(id) // re-asked whenever `id` changes + +
+ + {errored fallback(e) {

Couldn't load: {e.message}

} { + {loading fallback {

Loading…

} { +

{user.name}

+ }} + }} +
+} +``` + +Leave out the `errored` boundary and it does not compile: + +``` +profile.rx:10: error UNHANDLED_FAILURE [R-f-5] a reader of user.name (in ) can fail with Error and no catch or errored boundary above it discharges Error — wrap it in `errored fallback(e) { … } { … }`, or catch it: `… catch (e) => …` +``` + +Beyond this the language has structured stores with per-path writes +([spec/06](spec/06-stores.md)), optimistic updates and held values while new +data loads ([spec/05](spec/05-holds.md)), modules that own their state and +export actions to change it ([spec/09](spec/09-modules.md)), streamed SSR with +hydration ([spec/10](spec/10-server.md)), `server fn` for code that runs on +the server ([spec/12](spec/12-server-functions.md)), scoped `