source_lang/lib.rs
1//! # source_lang
2//!
3//! The multi-file coordinate layer of a compiler front-end. It holds many
4//! sources — files and in-memory buffers — in one [`SourceMap`], gives each a
5//! stable [`SourceId`], lays them out across a single global position space, and
6//! resolves any global [`BytePos`] back to the source and local offset it came
7//! from — or, in one step, to the source and a 1-based [`LineCol`].
8//!
9//! It is the layer above [`span_lang`]: a [`Span`] says *where in a buffer* an
10//! error is, and this crate says *which buffer*, so a diagnostic can name the
11//! file as well as the position. It owns source storage and coordinate mapping
12//! only — no lexing, no diagnostic rendering.
13//!
14//! ## Loading sources
15//!
16//! Sources come from three places, all funnelling through the same checks so a
17//! buffer and a file fail and succeed the same way:
18//! [`add`](SourceMap::add) for text already in hand,
19//! [`add_bytes`](SourceMap::add_bytes) for raw bytes validated as UTF-8, and
20//! [`add_file`](SourceMap::add_file) (with the `std` feature) for a path read
21//! from disk. Bad input is a defined [`SourceMapError`] — oversize, non-UTF-8,
22//! or an I/O failure — never a panic. [`set_max_source_len`](SourceMap::set_max_source_len)
23//! caps how much one untrusted source may load.
24//!
25//! ## Model
26//!
27//! Sources are placed end to end in the order they are added. The first occupies
28//! global offsets `0..len₀`, the next `len₀..len₀ + len₁`, and so on, so the
29//! ranges never overlap and the whole project shares one position space. Because
30//! each base is the running total, the sources stay sorted by offset and
31//! [`SourceMap::locate`] is a binary search — `O(log files)` — that borrows the
32//! resolved source rather than copying it. The space is 32 bits wide, so the
33//! combined length of every source is capped at `u32::MAX`; overrunning it is a
34//! defined [`SourceMapError`], never a silent wrap.
35//!
36//! A map only grows: it keeps every source's text and never reuses a range, so
37//! that every id and span it has handed out keeps resolving. Use one map per
38//! compilation. An editor or language server should build a fresh map for each
39//! revision it analyses rather than adding every edit to one long-lived map.
40//!
41//! ## Quickstart
42//!
43//! ```
44//! use source_lang::{BytePos, SourceMap};
45//!
46//! let mut map = SourceMap::new();
47//! let main = map.add("main.rs", "fn main() {}")?; // global 0..12
48//! let util = map.add("util.rs", "fn helper() {}")?; // global 12..26
49//!
50//! // Resolve a global position to its file and the local offset within it.
51//! let (id, local) = map.locate(BytePos::new(13)).expect("inside util.rs");
52//! assert_eq!(id, util);
53//! assert_eq!(local, BytePos::new(1)); // 13 - 12
54//!
55//! // The id is a stable handle back to the source.
56//! assert_eq!(map.source(main).unwrap().name(), "main.rs");
57//! # Ok::<(), source_lang::SourceMapError>(())
58//! ```
59//!
60//! ## Stability
61//!
62//! The public API is stable as of `1.0` and follows Semantic Versioning: no
63//! breaking changes before `2.0`, additions arrive in minor releases, and the MSRV
64//! (Rust 1.85) only rises in a minor. The full promise is in
65//! [`docs/API.md`](https://github.com/jamesgober/source-lang/blob/main/docs/API.md).
66
67#![cfg_attr(not(feature = "std"), no_std)]
68#![cfg_attr(docsrs, feature(doc_cfg))]
69#![deny(missing_docs)]
70#![forbid(unsafe_code)]
71
72extern crate alloc;
73
74mod error;
75mod file;
76mod id;
77mod lines;
78mod map;
79
80pub use error::SourceMapError;
81pub use file::SourceFile;
82pub use id::SourceId;
83pub use map::SourceMap;
84
85// Re-exported so a downstream consuming this crate's API does not also have to
86// name `span-lang` as a dependency just to spell the position and coordinate
87// types this crate returns.
88pub use span_lang::{BytePos, LineCol, LineIndex, Span};