purescript-rs is a learning compiler that compiles a growing subset of
PureScript to portable WebAssembly and runs it on a WASI 0.2 runtime. It is
built as a sequence of small, testable stages and is not a replacement for the
official PureScript compiler.
# Build, print, and run
cargo run -- build examples/basic.purs -o /tmp/basic.wasm
wasmtime run /tmp/basic.wasm; echo $? # prints 42
cargo run -- build examples/hello.purs -o /tmp/hello.wasm
wasmtime run /tmp/hello.wasm # prints "hello world"
# Frontend inspection, one stage at a time
cargo run -- lex examples/basic.purs
cargo run -- parse examples/basic.purs
cargo run -- ast examples/basic.purs
cargo run -- hir examples/resolved.purs
cargo run -- check examples/basic.purs
cargo run -- check-program-kinds examples/basic.purs
cargo run -- dump mir examples/basic.pursThe local default expects ../psrs-stdlib at the revision/content recorded in
stdlib.lock.json. Set PSRS_STDLIB_ROOT for an explicit development package.
See package and conformance setup.
build <file.purs>... resolves and links every listed module with the standard
library from the locked psrs-stdlib package and writes the artifact.
wat <file.purs>... renders the text form. dump <core|cc|mir> <file.purs> prints an intermediate IR for
debugging. A selected main :: Int returns its value as the process exit code.
The package-configured runner executes a selected main :: Effect a once,
discards its result, and returns 0; a trap still propagates.
For a guided, interactive walkthrough of P0 through P11, use the React
application in psrs-explorer/. It labels compact teaching
forms as curated and links to CLI commands for real compiler output.
Progress is stated as official-suite measurements, not as a summary impression.
The corpus is vendored under tests/upstream/, and
D-04 carries the full breakdown, including
what remains in each layer.
| Gate | Measured | Scope |
|---|---|---|
| L0/L1 lexing, layout, parsing | 904/908 | non-FFI layout, passing, failing, warning files; the four differences are recorded DEC-16 intentional differences |
| L2 resolution | 72/72 failing, 403/413 passing | official errorCodes; 6 passing files stop at P3 and 4 at P0; no harness blockers |
| L3 kinds | 44/48 failing | official kind errorCodes; measured 2026-10-18 |
| L4 types | 41/50 failing | official errorCodes; ExpectedType 2/2 since the annotation-position rule |
| L5 classes | 98/99 failing | official errorCodes; only ClassHeadNoVTA3 (#123) remains; measured 2026-10-18 |
| L6/M7 runtime | 287/340 programs | all exit 0; 53 do not agree; 74 support files remain program inputs, 25 FFI groups excluded; remeasured 2026-10-20 after the Control exception bridge, with agreeing/failing sets unchanged and all 365 roots accounted for; BigFunction compilation remains unverified (outer timeout; no compiler verdict) |
| M8 warnings, optimization | not measured | no scoreboard exists |
Frontend counts use source files; runtime counts use program roots with their support sources. The 2026-10-17 runtime remeasurement uses the release harness and records completion, not agreement with upstream output goldens. Its program denominator is not directly comparable with the earlier per-file totals.
Run the scoreboards yourself:
PSRS_ORACLE=annotations PSRS_REQUIRE_WASMTIME=1 \
cargo test -p psrs-driver --test suite -- --ignored --nocaptureThe compiler currently:
- inspects source through every frontend stage:
lex,layout,parse,ast,hir,check,check-program, andcheck-program-kinds; - lowers the supported subset through Typed Core, closure-converted CC IR, and a MIR/CFG to a validated WASI 0.2 Component Model artifact, and prints the corresponding WAT;
- exports
wasi:cli/run@0.2.12, somain's result becomes the process exit code throughwasi:cli/exit;loganderrorwrite towasi:cli/stdoutandwasi:cli/stderr, andnowreads the monotonic clock; - resolves, links, and type checks type classes, superclasses, instances, and
functional dependencies, then passes dictionaries through to Wasm — a
method reached through a superclass constraint returns the right value under
wasmtime, as does a call through a constrained function argument; - loads the PureScript standard library from the locked
psrs-stdlibpackage, exposing console, clock, random, process arguments and environment, filesystem, and sockets over WASI.
Verified working subsets, each with source tests and Wasmtime execution:
- functions with scalar, aggregate, and generic captures; direct and indirect calls; explicit adapters between concrete and erased higher-order values;
Int/Number/Boolean/Char/String/Unit, arithmetic and comparisons,let,if,case, anddo/adodesugaring;- data types and newtypes, including parameterized ADTs with the erased representation, multi-field constructor patterns in parameters, and coverage analysis that reports non-exhaustive witnesses;
- closed concrete records, canonical generic records, and arrays, with field reads, updates, and patterns;
- rank-2 through rank-4 polymorphism, roles and
Coercible, andderive newtype.
- frontend agreement — kind, type, and class checking still have the gaps recorded in D-04; warning-code agreement is not measured;
- runtime — open rows have no runtime layout, variants are not implemented, and the scalar and numeric operation set is partial (scalars);
- aggregates at the ABI boundary — the canonical ABI covers the mapped scalar, string, list, flags, handle, and variant shapes (canonical ABI), and the synthesized aggregate fixtures validate but do not yet execute;
- the standard library — the independent
psrs-stdlibpackage holds pinned official sources and target adapters; several foreign bindings remain unimplemented. Compiler-owned modules such asSafe.CoerceandUnsafe.Coerceresolve through their declared primitive interfaces.
| Crate | Responsibility |
|---|---|
psrs-span |
Source text, byte ranges, and line/column mapping. |
psrs-syntax |
Lexing, layout insertion, and parsing. |
psrs-cst |
Concrete syntax tree with source ranges. |
psrs-ast |
Normalized AST and CST-to-AST lowering. |
psrs-resolve |
Locals, same-module names, cross-module imports/exports, and whole-program module graphs into HIR. |
psrs-hir |
Resolved HIR nodes and stable declaration/local IDs. |
psrs-kind |
Kind inference and unification with official kind diagnostics. |
psrs-thir |
Typed expressions. |
psrs-typecheck |
Rank-1 and higher-rank inference, classes, instances, fundeps, roles, and coercions. |
psrs-desugar |
Operator desugaring while preserving HIR. |
psrs-core |
Typed Core and its HIR lowering. |
psrs-backend |
CC IR, MIR/CFG, structured Wasm encoding, WIT/ABI lowering, validation, and WAT. |
psrs-driver |
Wires the compiler passes together and loads the locked psrs-stdlib package. |
psrs-cli |
Source inspection, build, wat, and dump commands. |
The architecture defines twelve major passes across six long-lived IR families; see D-01.
The artifact contract is an explicit capability profile for a pinned wasmtime
release, not every feature a runtime happens to support. The stable profile
enables Wasm GC, reference types, typed function references, and the synchronous
WASI 0.2 Component Model path; SIMD, tail calls, exceptions, threads, memory64,
and WASI 0.3 stay disabled until their lowerings and tests land. Wasm GC is the
only language heap; linear memory is reserved for the canonical ABI boundary
(DEC-09). See
DEC-05 and
capability profile.
cargo fmt --all --check
cargo test --workspace
cargo clippy --workspace --all-targets -- -D warningsThe scoreboards under crates/psrs-driver/tests/suite/ are #[ignore]d and
read the vendored corpus. They need purs for the layout and parse boards and
wasmtime for the runtime board; each skips cleanly without its tool:
PSRS_ORACLE=annotations \
cargo test -p psrs-driver --test suite -- --ignored --nocapturePSRS_REQUIRE_WASMTIME=1 turns the runtime gate from a skip into a failure, so
CI cannot let an execution test silently pass without a runtime.
The optional upstream differential test checks the front end against the
official purs compiler on a small manifest of cases. It skips when purs or a
PureScript checkout is unavailable:
PURESCRIPT_REPO=/path/to/purescript cargo test -p psrs-driver --test upstreamdocs/ indexes the project documents, and the
authoring guide says where a new one goes and how it
is named.
| Location | Content |
|---|---|
docs/feature/ |
User-facing behavior and acceptance criteria: F-01, F-02, F-03. |
docs/design/ |
Implementation and IR architecture. Start at D-01 for the pass pipeline, D-04 for the official-suite roadmap, then the frontend and backend topics. |
docs/decision/ |
Only major, durable choices, from the policy onward. |
docs/implementation/ |
Per-topic acceptance checklists and the evidence behind each. |
tests/upstream/ |
The pinned official suite. |
AGENTS.md |
Workflow, code, validation, and project-iteration rules. |
D-04 is the normative record of what is
true; GitHub records what to do next. The work is tracked on the
PureScript→Wasm roadmap project
board, where a milestone per phase carries the order, gate: and area: labels
carry the cross-cutting view, dependencies carry the blocking edges, and the
Corpus cases field records how much of the official suite an issue recovers.
AGENTS.md describes how to pick, work, and close
an item, and
DEC-04 records why the
matrices are maintained.