The Crisp Programming Language
Explicit on demand, implicit by default.
Crisp (.crp) is a systems language that transpiles to Rust. You write compact source; crisp infers types, ownership, and error propagation, emits Rust, and rustc is the soundness boundary.
This is a Rust-hosted bootstrap compiler (v1.7.3) — public release track. It is not self-hosted yet (ROADMAP Phase 2 / milestone v2.0.0). The language document remains spec v0.2.0-draft — treat “spec-complete” claims cautiously; see known limitations and spec ↔ impl deltas.
Known Rust Result APIs from rust = true deps lower to Crisp ambient errors (CrispError::Thrown + ?) — see examples/rust_import, examples/net_http (#55).
Spec: docs/spec/CrispLang-SPECS-0.2.0.md
Quickstart: QUICKSTART.md
Web docs: crisp-lang.org · branch docs
Roadmap: ROADMAP.md
Changelog: CHANGELOG.md
Contributing: CONTRIBUTING.md
Security: SECURITY.md
Milestone: v1.7.3
License: MIT OR Apache-2.0 (LICENSE, LICENSE-MIT, LICENSE-APACHE).
Philosophy
Crisp is optimized for writing and local reading. Semantics are defined by lowering to Rust, not by a separate runtime or VM.
In practice that means:
- Types, borrows, lifetimes, and error sets are inferred globally when the source stays silent.
- When you need precision — public APIs, performance-sensitive paths, or ambiguous usage — you annotate, and the compiler treats those annotations as hard constraints.
revealis the “show your work” companion tocrisp: it prints inferred types, ownership (&/&mut), lifetimes, error sets, traits, and the emitted Rust that compact.crpsource leaves implicit. See QUICKSTART §10.
Crisp is a front end that produces Rust. Rust remains the authority on memory safety and data races. Crisp’s own borrow/region passes exist to drive good diagnostics and to decide what to emit (&, &mut, owned, .clone() fallbacks); they are not claimed as an independent soundness boundary. If generated Rust fails to compile, that is a crisp bug, not a user error.
Design goals: native code via rustc; HM-style type inference; deterministic global ownership dataflow; ambient fallible functions lowered to a uniform Result<T, CrispError>; compact syntax; tooling-first ergonomics.
Non-goals: scripting semantics; a GC; a stable library ABI across separately compiled units; pretending Crisp’s checker replaces rustc; claiming full enum/trait/shape coverage while those remain incomplete.
Architecture
Crisp v0.2.0 (spec) retargets from “direct-to-LLVM language” to source → CIR → Rust → native. The unit of compilation is the whole program or a sealed crate — inferred signatures depend on call sites, so ad-hoc separate compilation without a lockfile is not supported.
.crp sources
│
▼
Lexer → Parser → Resolve → Type inference → Ownership → Regions → Errors
│
▼
CIR (typed, ownership-resolved IR)
│
▼
Rust emission ──► rustc ──► native binary
│
└── probe emit + rustc (§7.6 fallbacks when ownership disagrees)
| Stage | Crate / tool | Role |
|---|---|---|
| Lex / parse | crisp-lexer, crisp-parser |
UTF-8 .crp, expression-based AST, spans |
| Resolve | crisp-resolve |
File modules, use, prelude, visibility |
| Typeck | crisp-typeck |
HM inference + constraint solving |
| Ownership | crisp-ownership |
Global usage → & / &mut / owned; §7.6 fallbacks |
| Regions / errors | crisp-regions, crisp-errors |
Lifetimes, ambient ! → CrispError |
| IR | crisp-cir |
Typed CIR consumed by emit |
| Emit | crisp-rust-emit |
Rust project under target/rust/, tests, crisp.lock |
| CLI | crisp |
check, emit, build, run, test |
| Inspect | reveal |
Companion CLI: inferred types/ownership/errors, traits, emitted Rust (QUICKSTART §10) |
| IDE | crisp-lsp |
Stdio LSP host (hover, inlay hints, diagnostics) + CrispAnalysis API (#56) |
Sealed crates (crisp.lock): a crate’s pub API has fully resolved signatures frozen at publish time. Downstream code analyzes against the lockfile, not re-inferred internals — the explicit tradeoff for whole-program inference inside a boundary.
Type vs ownership inference: HM-style unification handles types; ownership is a separate deterministic dataflow pass over the typed program. They are not unified — affine ownership does not compose with HM unification.
Language (summary)
| Area | Surface | Lowers to |
|---|---|---|
| Functions | named f(x) = … or ` |
x |
| Bindings | x := value |
let / let mut |
| Types | type T = { … }, float, int, str, … |
Rust structs / aliases / f64 / i64 / String |
| Generics | Prefer id(x: T), type Pair = { left: A, right: B }; <> pins / applies |
Rust type params (T: Clone on emit) |
| Errors | f() ! E, throw, catch |
Result<T, CrispError> |
| Modules | one file = one module; use m { f } |
generated mod tree |
| Tests | test, test_compile_fail |
injected #[test] in emitted crate |
| Async / FFI | async, await, extern "C" |
Tokio / extern blocks (see examples) |
Comments: -- and nested {- -}. String interpolation: "hello {name}". Exponentiation: ** → .powf().
Status
v1.7.3 — crisp.toml path deps (#105, examples/path_dep); crisp run cwd is the crate root, not target/rust (#106). v1.7.2: binop grouping (#99), nested type paths (#100), string match (#101), crisp test harness (#102). Function values remain from v1.7.0 (examples/closures, #72 / #87–#89).
Still open: crates.io republish (#66), repo visibility (#58); trait bounds / dyn Trait remain partial (#59). See ROADMAP.md.
MSRV: Rust 1.85 (rust-version in root Cargo.toml). CI runs Ubuntu + macOS on stable, plus an MSRV job.
Quick start
Install the compiler from crates.io (puts crisp and reveal on your PATH via ~/.cargo/bin):
You still need a Rust toolchain (1.85+) with cargo / rustc — Crisp lowers to a Cargo project and builds with rustc.
Hello world (no clone)
&&
Expected output:
"hello world"
From this repository
# already installed via cargo install crisp-lang, or:
# cargo install --path crates/crpc --locked
Other commands on a project:
Optional LSP:
See QUICKSTART.md for project layout, modules, tests, and fallible functions. crates.io notes: docs/CRATES_IO.md (#66).
Examples
| Example | Topics | Notes |
|---|---|---|
hello, math, float_demo, enums |
Shapes, implicit generics, integers, floats, enum + match | crisp test |
show_trait, trait_defaults, shapes |
Traits, defaults, data shapes | crisp test |
generics_implicit, generics, generics_pub, shapes_generic, shapes_user |
Implicit binders (preferred), pins, pub schemes, HasPosition + native ops / user Measure |
crisp test |
std_traits, rust_import, net_http |
Show/Eq/Ord, Rust crates, thin HTTP | |
defaults, inventory, server |
Struct defaults, domain modules, config | |
fallible, fallible_chain |
!, throw, catch, error chains |
|
vec_ops, data_pipeline |
vec stdlib, fallible IO |
|
patterns, match |
Pattern matching (literal-oriented today) | see limitations |
async_hello, async_spawn |
Async / Tokio | |
ffi, unsafe_math |
C FFI, unsafe |
|
sealed |
crisp.lock sealed public API |
|
kitchen_sink, ownership_demo |
Combined features | |
workshop |
Small multi-file workshop | crisp test |
design_patterns |
GoF-style multi-module patterns | crisp test / check |
abnormal_suite |
Compile-fail edge cases | typecheck / fail tests |
Repository layout
crates/ Rust compiler workspace (lexer → emit, crisp, lsp, reveal)
docs/ Spec, limitations, error catalog, web site scaffold
examples/ Sample .crp projects
std/ Standard library (Crisp prelude and modules)
tests/ Integration placeholders (fixtures live under crates/)
Contributing / building
See CONTRIBUTING.md. Spec conformance and e2e coverage live under crates/crisp-rust-emit/tests/ and crates/crpc/tests/.