span-lang 0.2.0

Source positions, spans, and byte-to-line/col mapping for language tooling.
Documentation

Installation

[dependencies]
span-lang = "0.2"

no_std targets disable the default std feature; the crate then relies only on core and alloc:

span-lang = { version = "0.2", 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 BytePosLineCol in O(log lines), handling \n and \r\n uniformly, with no allocation on the lookup path.

Correctness is held to the project invariants by property tests cross-checked against a naive reference resolver over UTF-8 input including multi-byte characters and CRLF.

Usage

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.

  • BytePos — a byte offset into one source.
  • Span — a half-open byte range with merge, contains, and ordering.
  • LineCol — a resolved 1-based line/column coordinate.
  • LineIndex — byte ↔ line/column resolution in O(log lines).

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.