https://github.com/jamesgober/span-lang
Source positions, spans, and byte-to-line/column mapping for language tooling.
https://github.com/jamesgober/span-lang
compiler language parser rust spans
Last synced: about 4 hours ago
JSON representation
Source positions, spans, and byte-to-line/column mapping for language tooling.
- Host: GitHub
- URL: https://github.com/jamesgober/span-lang
- Owner: jamesgober
- License: apache-2.0
- Created: 2026-06-19T01:19:34.000Z (20 days ago)
- Default Branch: main
- Last Pushed: 2026-06-19T02:44:35.000Z (19 days ago)
- Last Synced: 2026-06-19T03:05:45.702Z (19 days ago)
- Topics: compiler, language, parser, rust, spans
- Language: Rust
- Size: 33.2 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Funding: .github/FUNDING.yml
- License: LICENSE-APACHE
Awesome Lists containing this project
README
span-lang
SOURCE SPANS & POSITION MAPPING
span-lang provides the source-position substrate for language tooling: compact byte-offset spans, line/column resolution, and multi-file coordinate mapping. It is the foundational crate every later stage of a lexer, parser, or compiler references when reporting where something lives in source.
MSRV is 1.85+ (Rust 2024 edition).
Status: stable (v1.0.0). The full position, span, resolution, and Spanned<T> surface is implemented and property-tested, with the O(log lines) lookup verified by benchmark scaling. The public API is frozen under the SemVer promise: no item is removed or changed incompatibly before 2.0.0. See API.md and CHANGELOG.md.
## Installation
```toml
[dependencies]
span-lang = "1"
```
`no_std` targets disable the default `std` feature; the crate then relies only on `core` and `alloc`:
```toml
span-lang = { version = "1", default-features = false }
```
## Performance
A `Span` is a `Copy` value (two packed 32-bit offsets, eight bytes), and line/column resolution is a binary search over line starts — `O(log lines)`, never a re-scan of the source. Latest local Criterion means (`cargo bench`, Windows x86_64, Rust stable):
- **`Span::merge`** — ~0.6 ns/op
- **`LineIndex::offset`** (line/col → byte) — ~2.5 ns/op
- **`LineIndex::line_col`** (byte → line/col) — ~8.7 ns/op
- **`LineIndex::new`** — ~8.4 µs to index 1 000 lines (the only `O(n)` operation; lookups allocate nothing)
## Features
- **`BytePos`** — a 4-byte `Copy` byte offset; the atom every span is built from.
- **`Span`** — a half-open `start..end` byte range with `len`, `is_empty`, `contains`, ordering, and an associative, commutative `merge`. The `start <= end` invariant is enforced at construction.
- **`LineCol`** — a resolved 1-based line/column, where the column counts Unicode scalar values (never bytes, never inside a multi-byte sequence).
- **`LineIndex`** — built once per source; maps `BytePos` ↔ `LineCol` in `O(log lines)`, handling `\n` and `\r\n` uniformly, with no allocation on the lookup path. Also yields a line's text span for rendering.
- **`Spanned`** — a value paired with the span it came from, so a token or AST node carries its location without the value type knowing about positions.
An optional `serde` feature round-trips every position type; `Span` deserialises through its constructor, so a value from an untrusted source cannot violate the `start <= end` invariant. Correctness is held to the [project invariants](./docs/API.md#invariants) by property tests cross-checked against a naive reference resolver over UTF-8 input including multi-byte characters and CRLF.
## Usage
```rust
use span_lang::{LineIndex, Span};
let src = "fn main() {\n work();\n}\n";
// Spans are half-open byte ranges; merge covers both inputs.
let call = Span::new(16, 22);
assert_eq!(call.len(), 6);
// Resolve a byte offset to a human (line, column) coordinate.
let index = LineIndex::new(src);
let lc = index.line_col(call.start());
assert_eq!((lc.line, lc.col), (2, 5));
// The mapping is reversible.
assert_eq!(index.offset(lc), Some(call.start()));
```
## API Overview
For a complete reference with examples, see [`docs/API.md`](./docs/API.md).
- [`BytePos`](./docs/API.md#bytepos) — a byte offset into one source.
- [`Span`](./docs/API.md#span) — a half-open byte range with `merge`, `contains`, and ordering.
- [`LineCol`](./docs/API.md#linecol) — a resolved 1-based line/column coordinate.
- [`LineIndex`](./docs/API.md#lineindex) — byte ↔ line/column resolution in `O(log lines)`, plus per-line text spans.
- [`Spanned`](./docs/API.md#spanned) — a value paired with its `Span`.
## Contributing
See dev/DIRECTIVES.md for engineering standards and the definition of done. Before a PR: `cargo fmt --all`, `cargo clippy --all-targets --all-features -- -D warnings`, and `cargo test --all-features` must be clean.
License
Licensed under either of
-
Apache License, Version 2.0 — LICENSE-APACHE
-
MIT License — LICENSE-MIT
at your option.
COPYRIGHT © 2026 James Gober .