# The Crisp Programming Language
[](https://github.com/jose-compu/crisp/actions/workflows/ci.yml)
[](LICENSE)
[](CHANGELOG.md)
[](Cargo.toml)
[](.github/workflows/ci.yml)
[](docs/spec/CrispLang-SPECS-0.2.0.md)
[](https://crisp-lang.org/)
<p align="center">
<img src="assets/crisp-logo-square.jpg" alt="Crisp logo" width="320" />
</p>
> **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](docs/spec/CrispLang-SPECS-0.2.0.md)** — treat “spec-complete” claims cautiously; see [known limitations](docs/KNOWN_LIMITATIONS.md) and [spec ↔ impl deltas](docs/SPEC_IMPL_DELTA.md).
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](docs/spec/CrispLang-SPECS-0.2.0.md)
**Quickstart:** [QUICKSTART.md](QUICKSTART.md)
**Web docs:** [crisp-lang.org](https://crisp-lang.org/) · branch [`docs`](https://github.com/jose-compu/crisp/tree/docs)
**Roadmap:** [ROADMAP.md](ROADMAP.md)
**Changelog:** [CHANGELOG.md](CHANGELOG.md)
**Contributing:** [CONTRIBUTING.md](CONTRIBUTING.md)
**Security:** [SECURITY.md](SECURITY.md)
**Milestone:** [v1.7.3](https://github.com/jose-compu/crisp/milestones)
License: **MIT OR Apache-2.0** ([LICENSE](LICENSE), [LICENSE-MIT](LICENSE-MIT), [LICENSE-APACHE](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.
- **`reveal`** is the “show your work” companion to `crisp`: it prints inferred types, ownership (`&` / `&mut`), lifetimes, error sets, traits, and the emitted Rust that compact `.crp` source leaves implicit. See [QUICKSTART §10](QUICKSTART.md#10-inspect-what-the-compiler-inferred-reveal).
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)
```
| 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)
| Functions | named `f(x) = …` or `|x| …`; holes / trailing last-arg / `.field` / `.m()` | Rust `fn` or `move` closure / `impl Fn` |
| 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](https://github.com/jose-compu/crisp/issues/66)), repo visibility ([#58](https://github.com/jose-compu/crisp/issues/58)); trait bounds / `dyn Trait` remain partial ([#59](https://github.com/jose-compu/crisp/issues/59)). See [ROADMAP.md](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`):
```bash
cargo install crisp-lang --locked
crisp --version
```
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)
```bash
mkdir -p hello/src && cd hello
cat > crisp.toml <<'EOF'
[package]
name = "hello"
version = "0.1.0"
edition = "2026"
[build]
target = "rust"
runtime = "tokio"
error_model = "enum"
EOF
cat > src/main.crp <<'EOF'
shape Named = {
name: str
}
type Guest = {
name: str = "world"
}
id(x: T) = x
greet(who: Named) = "hello {who.name}"
pub main() = {
world := Guest {}
print(id(greet(world)))
}
EOF
crisp run .
```
Expected output:
```
"hello world"
```
### From this repository
```bash
git clone https://github.com/jose-compu/crisp.git
cd crisp
# already installed via cargo install crisp-lang, or:
# cargo install --path crates/crpc --locked
crisp run examples/hello
```
Other commands on a project:
```bash
crisp check . # resolve + typecheck
crisp emit . # write Rust under target/rust/
crisp build . # emit + cargo build
```
Optional LSP:
```bash
cargo install crisp-lsp --locked
```
See [QUICKSTART.md](QUICKSTART.md) for project layout, modules, tests, and fallible functions. crates.io notes: [docs/CRATES_IO.md](docs/CRATES_IO.md) ([#66](https://github.com/jose-compu/crisp/issues/66)).
## Examples
| `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
```bash
cargo build --release -p crisp-lang
cargo test --workspace --verbose
cargo fmt --all --check
cargo clippy --workspace -- -D warnings
```
See [CONTRIBUTING.md](CONTRIBUTING.md). Spec conformance and e2e coverage live under `crates/crisp-rust-emit/tests/` and `crates/crpc/tests/`.