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//! ## Stability
56//!
57//! The public API is stable as of `1.0` and follows Semantic Versioning: no
58//! breaking changes before `2.0`, additions arrive in minor releases, and the MSRV
59//! (Rust 1.85) only rises in a minor. The full promise is in
60//! [`docs/API.md`](https://github.com/jamesgober/source-lang/blob/main/docs/API.md).
61
62#![cfg_attr(not(feature = "std"), no_std)]
63#![cfg_attr(docsrs, feature(doc_cfg))]
64#![deny(missing_docs)]
65#![forbid(unsafe_code)]
66
67extern crate alloc;
68
69mod error;
70mod file;
71mod id;
72mod map;
73
74pub use error::SourceMapError;
75pub use file::SourceFile;
76pub use id::SourceId;
77pub use map::SourceMap;
78
79// Re-exported so a downstream consuming this crate's API does not also have to
80// name `span-lang` as a dependency just to spell the position and coordinate
81// types this crate returns.
82pub use span_lang::{BytePos, LineCol, LineIndex, Span};