driver-lang 1.0.0

Compiler driver and session orchestration.
Documentation
# driver-lang v0.2.0 — The Core

**The first release with domain logic.** v0.1.0 was the scaffold; v0.2.0 is the driver itself — a [`Session`](../API.md#session) that carries a compile's configuration and diagnostics, a [`Stage`](../API.md#stage) trait for a single phase, and a [`Pipeline`](../API.md#pipeline) that composes stages with their types checked at compile time. Every public item has rustdoc with a runnable example, the invariants are property-tested, and there is a Criterion suite and a worked example. The surface is designed here; `1.0.0` freezes it.

## What is driver-lang?

The driver that ties a compiler's phases into one run. A compiler is a sequence of phases — lex, parse, resolve names, check types, lower, emit — each taking the previous phase's artifact and producing the next. driver-lang is the machinery that holds those phases together: it carries the shared configuration and diagnostics, threads each artifact into the next phase, and stops cleanly when a phase fails. It owns no phases and wires no first-party dependency; a language plugs its own lexer, parser, and backend in as stages. `no_std` (needs only `alloc`), no `unsafe` (`#![forbid(unsafe_code)]`).

## What's new in 0.2.0

### `Session<C>` — configuration and diagnostics for one run

Every stage receives `&mut Session<C>`. It holds the compilation configuration `C` (whatever a language needs) and the diagnostics the phases emit, and it keeps an incremental error count so the driver can check `has_errors` between phases in O(1).

```rust
use driver_lang::Session;

let mut session = Session::new("x86_64-unknown-linux-gnu");
session.warn("unused import `std::mem`");
session.error("cannot find type `Foo`");

assert_eq!(session.config(), &"x86_64-unknown-linux-gnu");
assert_eq!(session.error_count(), 1);   // the warning does not count
assert!(session.has_errors());
```

`abort_if_errors` is the checkpoint primitive — the "aborting due to N previous errors" step at the end of a phase. Because a stage can emit several errors before it is checked, one checkpoint reports them all at once instead of stopping at the first:

```rust
# use driver_lang::Session;
let mut session = Session::new(());
session.error("first");
session.error("second");
let err = session.abort_if_errors().unwrap_err();
assert_eq!(err.message(), "aborting due to 2 previous errors");
```

### `Stage<C>` — one compilation phase

A stage has an `Input` type, an `Output` type, a stable `name`, and a `run` that consumes the input, uses the session, and produces the output. Unlike a *pass* (which rewrites one type in place), a stage **changes the type** as the program moves down the pipeline — a lexer is `Stage<Input = Source, Output = Tokens>`, a parser is `Stage<Input = Tokens, Output = Ast>`. That type-threading is exactly what a driver exists to manage, and it is why this is a separate crate from [`pass-lang`](https://crates.io/crates/pass-lang).

### `Pipeline<C, S>` — stages composed, checked at compile time

`then` appends a stage whose input matches the current output. The match is a type bound — a pipeline that feeds tokens into a stage expecting an AST does not compile — and the whole pipeline is one monomorphized type, so composing stages adds no runtime indirection.

```rust
use driver_lang::{DriverError, Pipeline, Session, Stage};

struct Lex;
impl Stage<()> for Lex {
    type Input = &'static str;
    type Output = Vec<i64>;
    fn name(&self) -> &'static str { "lex" }
    fn run(&mut self, i: &'static str, _s: &mut Session<()>) -> Result<Vec<i64>, DriverError> {
        i.split_whitespace()
            .map(|w| w.parse().map_err(|_| DriverError::new("not an integer")))
            .collect()
    }
}
struct Sum;
impl Stage<()> for Sum {
    type Input = Vec<i64>;
    type Output = i64;
    fn name(&self) -> &'static str { "sum" }
    fn run(&mut self, i: Vec<i64>, _s: &mut Session<()>) -> Result<i64, DriverError> {
        Ok(i.iter().sum())
    }
}

let mut driver = Pipeline::new(Lex).then(Sum);
let mut session = Session::new(());
assert_eq!(driver.run("1 2 3 4", &mut session).unwrap(), 10);
```

On the first stage that returns a [`DriverError`](../API.md#drivererror), the run stops and the error names the failing stage; diagnostics emitted before the failure remain in the session.

### `Diagnostic`, `Severity`, `DriverError`

A [`Diagnostic`](../API.md#diagnostic) is a [`Severity`](../API.md#severity) (`Error` / `Warning` / `Note`) plus a `Cow<'static, str>` message, so a constant message never allocates. Only `Error` counts toward the abort checkpoint. A [`DriverError`](../API.md#drivererror) is the control-flow failure that ends a run, carrying the message and the stage the pipeline stamps in — distinct from a diagnostic, which is a record a stage may keep going after.

### `serde` feature

Behind the opt-in `serde` feature, `Severity` and `Diagnostic` derive `serde::Serialize`, so a run's diagnostics can be serialized for tooling. Enabling it does not change any existing behavior.

### Worked example

[`examples/calculator.rs`](../../examples/calculator.rs) is a three-stage driver — lex, cap-check against a configured maximum, sum — that renders its diagnostics at the end. `cargo run --example calculator` shows a clean run, a run that aborts at the cap check reporting two errors at once, and a run that fails at lex.

## Breaking changes

**None.** v0.1.0 exposed no API; this is the first release with a public surface.

## Performance

A pipeline is a single monomorphized type — no boxing, no `dyn` dispatch — so driving it costs no more than calling the stages by hand. The session keeps its error tally incrementally. Latest local Criterion means (`cargo bench --bench bench`, Linux x86_64 / WSL2, Rust stable, release build):

| Workload | Time |
|----------|-----:|
| `Double → Increment → Sum` pipeline, 16 elements | ~15 ns |
| `Double → Increment → Sum` pipeline, 256 elements | ~100 ns |
| `Double → Increment → Sum` pipeline, 4096 elements | ~1.16 µs |
| Emit 8 diagnostics into a session | ~36 ns |
| Emit 128 diagnostics into a session | ~261 ns |
| Emit 1024 diagnostics into a session | ~991 ns |

Numbers vary by CPU and environment; run the suite on your target to establish a baseline. The pipeline benchmark clones its input each iteration, so it includes the allocation the two mapping stages consume.

## Verification

Run on Windows x86_64, with testing on Linux (WSL2 Ubuntu), Rust stable and the 1.85 MSRV; the same commands run in the CI matrix across Linux, macOS, and Windows:

```bash
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --all-features -- -D warnings
cargo clippy --no-default-features --lib -- -D warnings
cargo test
cargo test --all-features
cargo test --no-default-features --lib
cargo build --no-default-features --lib
cargo run --example calculator
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo deny check
```

All green. Counts at this tag:

- Default features: 37 unit + 5 integration + 3 property + 30 doctests.
- `--no-default-features` (lib): 37 unit (the crate is `no_std` + `alloc`).

## What's next

- **1.0.0 — API freeze.** Mark `docs/API.md` stable, record the SemVer promise, and lock the surface for the `1.x` series. No functional change is planned; the design is small and settled.

## Installation

```toml
[dependencies]
driver-lang = "0.2"

# no_std:
driver-lang = { version = "0.2", default-features = false }

# with serde derives on Severity and Diagnostic:
driver-lang = { version = "0.2", features = ["serde"] }
```

MSRV: Rust 1.85 (2024 edition).

## Documentation

- [README](https://github.com/jamesgober/driver-lang/blob/main/README.md)
- [API Reference](https://github.com/jamesgober/driver-lang/blob/main/docs/API.md)
- [CHANGELOG](https://github.com/jamesgober/driver-lang/blob/main/CHANGELOG.md)

---

**Full diff:** [`v0.1.0...v0.2.0`](https://github.com/jamesgober/driver-lang/compare/v0.1.0...v0.2.0).
**Changelog:** [`CHANGELOG.md`](https://github.com/jamesgober/driver-lang/blob/main/CHANGELOG.md#020---2026-07-01).