# repl-lang v1.0.0 — API Freeze
**The 0.2.0 surface, declared stable.** v1.0.0 freezes the public API of
repl-lang under Semantic Versioning: no breaking changes before `2.0`. The
freeze review changed no code — every type a host constructs or matches was
already either opaque or deliberately closed — so this release records the
contract: the surface, the behaviour that belongs to it, and the MSRV.
## What is repl-lang?
The state of a read-eval-print loop, with the language and the terminal left
out. A `Session` runs a language's pipeline on each entry as it is typed, uses
the pipeline's own parse errors to tell "unfinished" from "wrong", and commits
every finished entry to one source map so diagnostics can point anywhere in the
session. An `Editor` handles the line being typed — motion by visible
character and by word, kill and yank, history — driven by `Edit` commands the
host maps from its own key events. It is one of the FEAT-tier crates of the
`-lang` language-construction family, built on `lexer-lang`, `diag-lang`,
`source-lang`, and `unicode-lang`.
## The stability promise
Frozen until `2.0`:
- **`Session`** — `DEFAULT_LIMIT`, `new`, `with_limit`, `feed`, `cancel`,
`is_pending`, `pending`, `len`, `is_empty`, `entry`, `sources`, `limit`.
- **`Input`** — `text`, `base`, `span`, `number`, `cursor`, `is_incomplete`.
- **`Status`** (`Complete`, `Incomplete`) and **`Feed`** (`Complete`,
`Incomplete`, `Empty`) — exhaustive by design.
- **`Entry`** — `number`, `id`, `span`.
- **`SessionError`** — `TooLong`, `SpaceExhausted`; `#[non_exhaustive]`.
- **`Editor`** — `DEFAULT_HISTORY`, `new`, `with_history`, `line`, `cursor`,
`column`, `apply`, `insert`, `clear`, `submit`, `add_history`, `history`.
- **`Edit`** — sixteen commands; `#[non_exhaustive]`.
- The re-exports `Cursor`, `Diagnostic`, `SourceMap`, `SourceFile`,
`SourceId`, `Span`, `BytePos`.
The behaviour is part of the contract too: entries named `<repl:N>` and laid
out end to end, exactly at the `Input::base` the pipeline was given; one
trailing line terminator normalised; blank lines skipped only when nothing is
pending; refused lines discarding the pending entry without calling the
pipeline; the exact `is_incomplete` rule; the 1 MiB default entry limit and
the 1000-entry default history; and the editor's display-unit, word, history,
and draft rules. MSRV 1.85 is a compatibility surface: a rise is a minor
release, never a patch.
Not promised: `Display` wording of errors, `Debug` output, widths and word
classes of characters whose Unicode properties change in a later Unicode
version, and performance figures. The full list is in
[`docs/API.md#stability`](https://github.com/jamesgober/repl-lang/blob/main/docs/API.md#stability).
## Why `Status` and `Feed` stay exhaustive
A host matches `Feed` in every read loop, so making it `#[non_exhaustive]`
would force a wildcard arm into every REPL ever written against the crate. The
set is closed by design — an entry is finished, unfinished, or there was
nothing to evaluate — and a new pipeline verdict in `Status` would need a new
`Feed` outcome to report it. Both stay exhaustive, and any change to either is
a `2.0` change. `Edit` and `SessionError`, which hosts construct or report
rather than branch on exhaustively, keep `#[non_exhaustive]` so new editing
commands and new refusal reasons can arrive in `1.x`.
## Since 0.2.0
The code is unchanged. The documentation now carries the contract:
- `docs/API.md` is marked stable and ends with a Stability section listing the
frozen surface, the contractual behaviour, and what is not promised.
- The crate-level documentation links to it from a new Stability section.
- Install snippets use `repl-lang = "1"`, and the README performance table
reports Windows and Linux side by side.
A complete host loop, unchanged from 0.2.0 and now covered by the promise:
```rust
use diag_lang::{Diagnostic, Label, Renderer, Severity};
use repl_lang::{Edit, Editor, Feed, Session, Span, Status};
let mut editor = Editor::new();
let mut session = Session::new();
let mut printed = Vec::new();
for line in ["let x = (1 +", "2)", "y"] {
editor.insert(line);
let feed = session.feed(editor.submit(), |input| {
let open = input.text().matches('(').count();
if open > input.text().matches(')').count() {
return Status::Incomplete;
}
let text = input.text().trim_end();
Status::Complete(if text == "y" {
let at = input.base().to_u32();
Err(Diagnostic::new(Severity::Error, "cannot find `y`", Label::new(Span::new(at, at + 1), "not defined")))
} else {
Ok(text.replace('\n', " "))
})
})?;
match feed {
Feed::Complete { value: Ok(text), .. } => printed.push(text),
Feed::Complete { value: Err(diagnostic), .. } => {
printed.push(Renderer::new().render(&diagnostic, session.sources()));
}
Feed::Incomplete | Feed::Empty => {}
}
}
assert_eq!(printed[0], "let x = (1 + 2)");
assert!(printed[1].contains("<repl:2>:1:1"));
assert!(editor.apply(Edit::HistoryPrev));
assert_eq!(editor.line(), "y");
# Ok::<(), repl_lang::SessionError>(())
```
## Performance
Measured with the bundled Criterion benchmarks (x86_64, Rust stable, release
profile):
| `session/entries/100` — feed, check, and commit 100 entries | ~6.5 µs | ~3.8 µs |
| `session/continuation/lines=8` — one entry over 8 lines | ~0.47 µs | ~0.27 µs |
| `session/feed_incomplete/errors=4` — feed, `is_incomplete`, cancel | ~81 ns | ~48 ns |
| `editor/type/80` — an 80-column line, key by key | ~0.36 µs | ~0.23 µs |
| `editor/word_motion/line=800` — word-left and word-right across a long line | ~7.4 µs | ~4.9 µs |
| `editor/submit/history_full` — submit into a full 100-entry ring | ~32 ns | ~23 ns |
| `editor/column/line=900` — cursor column on a mixed-width line | ~1.7 µs | ~1.4 µs |
## Verification
Run on Windows x86_64 and Linux (WSL2 Ubuntu), Rust stable and the 1.85 MSRV;
macOS runs through the CI matrix:
```bash
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --all-features -- -D warnings
cargo clippy --lib --no-default-features -- -D warnings
cargo test
cargo test --all-features
cargo test --no-default-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo build --examples
cargo +1.85 test --all-features
cargo +1.85 clippy --all-targets --all-features -- -D warnings
cargo +1.85 fmt --all -- --check
cargo bench --bench bench
cargo deny check
cargo audit
cargo publish --dry-run
```
All green. Counts at this tag: 62 unit + 19 integration + 4 property + 99
doctests, the doctests covering every `rust` example in `README.md` and
`docs/API.md`.
## Installation
```toml
[dependencies]
repl-lang = "1"
diag-lang = "1"
# no_std (needs only alloc):
# repl-lang = { version = "1", default-features = false }
```
MSRV: Rust 1.85.
## Documentation
- [README](https://github.com/jamesgober/repl-lang/blob/main/README.md)
- [API Reference](https://github.com/jamesgober/repl-lang/blob/main/docs/API.md)
- [Stability](https://github.com/jamesgober/repl-lang/blob/main/docs/API.md#stability)
- [CHANGELOG](https://github.com/jamesgober/repl-lang/blob/main/CHANGELOG.md)
---
**Full diff:** [`v0.2.0...v1.0.0`](https://github.com/jamesgober/repl-lang/compare/v0.2.0...v1.0.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/repl-lang/blob/main/CHANGELOG.md#100---2026-07-08).