# source-lang v0.2.0 — Source storage & global position space

**The core, front-loaded.** v0.2.0 turns the scaffold into a working source map.
It introduces the `SourceMap` that holds many sources, the stable `SourceId` that
names each, and the single global position space they share — together with the
`O(log files)` resolver that maps a global position back to its source and local
offset. This was the hard part of the roadmap, and the roadmap put it first on
purpose: get the coordinate bookkeeping wrong and every multi-file diagnostic
points at the wrong place, so it is proven now, against a naive scan, before any
of the easier load/store surface is built on top of it.

## What is source-lang?

The multi-file coordinate layer of a compiler front-end. It loads files and
in-memory buffers, gives each a stable `SourceId`, lays them out in one global
position space, and maps any global position back to the source and local offset it
came from. It is the layer above `span-lang`: a `Span` says *where in a buffer* an
error is, source-lang says *which buffer*, so a diagnostic can name the file as well
as the position. It owns source storage and coordinate mapping only.

## What's new in 0.2.0

### `SourceMap` — many sources, one position space

The central type. Sources are added end to end and laid out across a single global
position space: the first occupies `0..len₀`, the next `len₀..len₀ + len₁`, and so
on. The ranges never overlap, and a single global `BytePos` names a point across the
whole project.

```rust
use source_lang::{BytePos, SourceMap};

let mut map = SourceMap::new();
let main = map.add("main.rs", "fn main() {}")?;   // global 0..12
let util = map.add("util.rs", "fn helper() {}")?; // global 12..26

let (id, local) = map.locate(BytePos::new(13)).expect("inside util.rs");
assert_eq!(id, util);
assert_eq!(local, BytePos::new(1)); // 13 - 12
# Ok::<(), source_lang::SourceMapError>(())
```

`add` takes anything that converts into a `Box<str>` (a `String` or a `&str`), so
the map owns the text and everything above it borrows `&str` back for the map's
lifetime — no re-reading, no copying on lookup.

### `locate` — `O(log files)`, allocation-free

Because each source's base is the running total of all earlier sources, the
internal list is always sorted by offset. `locate` is therefore a binary search over
it, not a linear scan, and it borrows the resolved source rather than copying it.
The benchmark confirms the scaling: across a 4096× increase in file count
(16 → 65,536 sources), per-lookup cost grows about 11×, not linearly — the shape of
`O(log files)`, with the slope coming from cache behaviour rather than comparisons.

Membership is half-open: a source covering `start..end` contains `start` but not
`end`, so the boundary between two adjacent sources resolves to the second, never to
both, and a position past the last byte resolves to `None` rather than panicking.

### `SourceId` — a stable, opaque handle

Adding a source returns a `SourceId`: a 32-bit, `Copy` handle that stays valid for
the life of the map, because sources are only ever appended. It is opaque — there is
no public constructor — so an id can only come from a map that holds the source it
names. Store it in a token, an AST node, or a cached diagnostic and resolve it later
with `source`.

### `SourceMapError` — a defined overflow, never a silent wrap

The global space is 32 bits wide — the same envelope a single `BytePos` addresses —
so the combined length of every source is capped at `u32::MAX`. Adding a source that
would overrun the remaining space returns `SourceMapError::SpaceExhausted { needed,
available }` and leaves the map unchanged, rather than wrapping a base offset into a
neighbour's range. The enum is `#[non_exhaustive]`, ready for the file-loading
failures a later phase adds.

### `span-lang`, wired and used

The `span-lang` dependency is now real, not reserved: global positions and local
offsets are its `BytePos`, and each source's footprint is its `Span`. Both types are
re-exported from the crate root, so a downstream consuming source-lang's API does
not also have to name `span-lang` as a dependency.

### Property tests against a naive resolver

`tests/properties.rs` holds the section-4 invariants to a deliberately naive
linear-scan oracle over a wide `proptest` input space:

- `locate` agrees with the naive scan on every probed position, in range and past
  the end.
- Every in-range position round-trips: the located base plus local offset
  reconstructs the global position, and the offset lands inside the file's text.
- Source ranges never overlap, and at most one source contains any position.
- Ids are unique and dense — `iter` yields exactly `0..len`, in the order `add`
  returned them.

## Breaking changes

**None.** v0.1.0 had no public surface; everything here is additive.

## Verification

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

```bash
cargo fmt --all -- --check
cargo clippy --all-targets -- -D warnings
cargo clippy --all-targets --all-features -- -D warnings
cargo test
cargo test --all-features
cargo build --no-default-features
cargo +1.85 build --all-features
RUSTDOCFLAGS="-D warnings" cargo doc --no-deps --all-features
cargo audit
cargo deny check
```

All green. Counts at this tag:

- Default features: 16 unit + 4 property + 17 doctests.
- `--all-features`: identical (serde adds no runtime surface yet).

## What's next

- **0.3.0 — File loading & line integration.** Load sources from disk and in-memory
  buffers behind one seam, with defined errors for missing/oversize/truncated input,
  and integrate `span-lang`'s `LineIndex` so a global position resolves to file +
  line/column in one step.

## Installation

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

MSRV: Rust 1.85.

## Documentation

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

---

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