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//! ## Quickstart
37//!
38//! ```
39//! use source_lang::{BytePos, SourceMap};
40//!
41//! let mut map = SourceMap::new();
42//! let main = map.add("main.rs", "fn main() {}")?; // global 0..12
43//! let util = map.add("util.rs", "fn helper() {}")?; // global 12..26
44//!
45//! // Resolve a global position to its file and the local offset within it.
46//! let (id, local) = map.locate(BytePos::new(13)).expect("inside util.rs");
47//! assert_eq!(id, util);
48//! assert_eq!(local, BytePos::new(1)); // 13 - 12
49//!
50//! // The id is a stable handle back to the source.
51//! assert_eq!(map.source(main).unwrap().name(), "main.rs");
52//! # Ok::<(), source_lang::SourceMapError>(())
53//! ```
54
55#![cfg_attr(not(feature = "std"), no_std)]
56#![cfg_attr(docsrs, feature(doc_cfg))]
57#![deny(missing_docs)]
58#![forbid(unsafe_code)]
59
60extern crate alloc;
61
62mod error;
63mod file;
64mod id;
65mod map;
66
67pub use error::SourceMapError;
68pub use file::SourceFile;
69pub use id::SourceId;
70pub use map::SourceMap;
71
72// Re-exported so a downstream consuming this crate's API does not also have to
73// name `span-lang` as a dependency just to spell the position and coordinate
74// types this crate returns.
75pub use span_lang::{BytePos, LineCol, LineIndex, Span};