Skip to main content

symbol_lang/
lib.rs

1//! # symbol_lang
2//!
3//! The name-resolution substrate for the `-lang` family: a lexically-scoped
4//! [`SymbolTable`] that maps interned names to bindings, and ready-made
5//! diagnostics for the two name errors every language reports. It is generic over
6//! the binding — the language decides what a name resolves *to* — so one scope
7//! engine serves every language built on it.
8//!
9//! ## Model
10//!
11//! A resolver walks its own syntax tree and threads a [`SymbolTable`]: it enters a
12//! scope on the way into a block, [`define`](SymbolTable::define)s the names that
13//! block introduces, [`lookup`](SymbolTable::lookup)s references outward through the
14//! enclosing scopes, and exits the scope on the way out (or wraps the block in
15//! [`scoped`](SymbolTable::scoped)). A name resolves to the nearest enclosing
16//! binding, so an inner definition shadows an outer one while its scope is open.
17//! Names are [`intern_lang`] [`Symbol`]s, so a lookup compares integers.
18//!
19//! When a name does not resolve, or is defined twice, [`unresolved_name`] and
20//! [`duplicate_definition`] turn that into a source-annotated [`diag_lang`]
21//! [`Diagnostic`].
22//!
23//! ## Quickstart
24//!
25//! ```
26//! use symbol_lang::{duplicate_definition, unresolved_name, SymbolTable};
27//! use intern_lang::Interner;
28//! use diag_lang::Span;
29//!
30//! let mut names = Interner::new();
31//! let x = names.intern("x");
32//! let y = names.intern("y");
33//!
34//! // Bindings can be anything — here, the span where each name was defined.
35//! let mut table: SymbolTable<Span> = SymbolTable::new();
36//! table.define(x, Span::new(0, 1));
37//!
38//! // Resolve a reference.
39//! assert!(table.lookup(x).is_some());
40//! assert!(table.lookup(y).is_none());
41//!
42//! // Turn an unresolved reference into a diagnostic.
43//! let diag = unresolved_name("y", Span::new(10, 11));
44//! assert_eq!(diag.message(), "cannot find `y` in this scope");
45//!
46//! // A duplicate definition links both sites.
47//! if let Some(first) = table.lookup_local(x).copied() {
48//!     let _ = duplicate_definition("x", Span::new(20, 21), first);
49//! }
50//! ```
51//!
52//! ## Features
53//!
54//! - `std` (default) — the standard library; without it the crate is `no_std`
55//!   (it always needs `alloc`). Forwards to `diag-lang/std` and `intern-lang/std`.
56//!
57//! ## Stability
58//!
59//! The public surface is being designed across the 0.x series and freezes at
60//! `1.0.0`, after which it follows Semantic Versioning: no breaking changes before
61//! `2.0`, additions arrive in minor releases, and the MSRV (Rust 1.85) only rises
62//! in a minor. The frozen surface is catalogued in
63//! [`docs/API.md`](https://github.com/jamesgober/symbol-lang/blob/main/docs/API.md).
64
65#![cfg_attr(not(feature = "std"), no_std)]
66#![cfg_attr(docsrs, feature(doc_cfg))]
67#![deny(missing_docs)]
68#![forbid(unsafe_code)]
69
70extern crate alloc;
71
72mod diagnostics;
73mod table;
74
75pub use diagnostics::{duplicate_definition, unresolved_name};
76pub use table::SymbolTable;
77
78// Re-exported so a resolver can name the key, span, and error types this crate's
79// API uses without also having to depend on `intern-lang` and `diag-lang`.
80pub use diag_lang::{Diagnostic, Span};
81pub use intern_lang::Symbol;