Skip to main content

sim_kernel/
id.rs

1//! Stable ids and the [`Symbol`] type used across the kernel.
2//!
3//! Defines the typed id wrappers (lib, class, codec, shape, ...), the
4//! well-known core class ids, and the symbol contract that names every runtime
5//! entity. Symbols are reference-counted (`Arc<str>`) rather than interned:
6//! there is no global dedup table, so equal names may back distinct
7//! allocations.
8
9use std::sync::Arc;
10
11use thiserror::Error;
12
13/// Stable id of a loaded library.
14#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
15pub struct LibId(pub u32);
16
17/// Stable id of a registered class.
18#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
19pub struct ClassId(pub u32);
20
21/// [`ClassId`] of the core `class` metaclass.
22pub const CORE_CLASS_CLASS_ID: ClassId = ClassId(0);
23/// [`ClassId`] of the core `nil` class.
24pub const CORE_NIL_CLASS_ID: ClassId = ClassId(1);
25/// [`ClassId`] of the core `bool` class.
26pub const CORE_BOOL_CLASS_ID: ClassId = ClassId(2);
27/// [`ClassId`] of the core `number` class.
28pub const CORE_NUMBER_CLASS_ID: ClassId = ClassId(3);
29/// [`ClassId`] of the core `symbol` class.
30pub const CORE_SYMBOL_CLASS_ID: ClassId = ClassId(4);
31/// [`ClassId`] of the core `string` class.
32pub const CORE_STRING_CLASS_ID: ClassId = ClassId(5);
33/// [`ClassId`] of the core `bytes` class.
34pub const CORE_BYTES_CLASS_ID: ClassId = ClassId(6);
35/// [`ClassId`] of the core `list` class.
36pub const CORE_LIST_CLASS_ID: ClassId = ClassId(7);
37/// [`ClassId`] of the core `table` class.
38pub const CORE_TABLE_CLASS_ID: ClassId = ClassId(8);
39/// [`ClassId`] of the core `expr` class.
40pub const CORE_EXPR_CLASS_ID: ClassId = ClassId(9);
41/// [`ClassId`] of the core `function` class.
42pub const CORE_FUNCTION_CLASS_ID: ClassId = ClassId(10);
43/// [`ClassId`] of the core `shape` class.
44pub const CORE_SHAPE_CLASS_ID: ClassId = ClassId(11);
45/// [`ClassId`] of the core `thunk` class.
46pub const CORE_THUNK_CLASS_ID: ClassId = ClassId(12);
47/// [`ClassId`] of the core `eval-request` class.
48pub const CORE_EVAL_REQUEST_CLASS_ID: ClassId = ClassId(13);
49/// [`ClassId`] of the core `eval-reply` class.
50pub const CORE_EVAL_REPLY_CLASS_ID: ClassId = ClassId(14);
51/// [`ClassId`] of the core `macro` class.
52pub const CORE_MACRO_CLASS_ID: ClassId = ClassId(15);
53/// [`ClassId`] of the core `shape-match` class.
54pub const CORE_SHAPE_MATCH_CLASS_ID: ClassId = ClassId(16);
55/// [`ClassId`] of the core `codec` class.
56pub const CORE_CODEC_CLASS_ID: ClassId = ClassId(17);
57/// [`ClassId`] of the core `help` class.
58pub const CORE_HELP_CLASS_ID: ClassId = ClassId(18);
59/// [`ClassId`] of the core `test` class.
60pub const CORE_TEST_CLASS_ID: ClassId = ClassId(19);
61/// [`ClassId`] of the core `number-domain` class.
62pub const CORE_NUMBER_DOMAIN_CLASS_ID: ClassId = ClassId(20);
63/// [`ClassId`] of the core local `eval-fabric` class.
64pub const CORE_LOCAL_EVAL_FABRIC_CLASS_ID: ClassId = ClassId(21);
65/// [`ClassId`] of the core `sequence` class.
66pub const CORE_SEQUENCE_CLASS_ID: ClassId = ClassId(22);
67/// [`ClassId`] of the core `card` class.
68pub const CORE_CARD_CLASS_ID: ClassId = ClassId(23);
69
70/// Stable id of a registered function.
71#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
72pub struct FunctionId(pub u32);
73
74/// Stable id of a registered macro.
75#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
76pub struct MacroId(pub u32);
77
78/// Stable id of a single overload case within a function.
79#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
80pub struct CaseId(pub u32);
81
82/// Stable id of a registered shape.
83#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
84pub struct ShapeId(pub u32);
85
86/// Stable id of a registered codec.
87#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
88pub struct CodecId(pub u32);
89
90/// Stable id of a registered number domain.
91#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
92pub struct NumberDomainId(pub u32);
93
94/// Stable id of a registered opaque site value.
95#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
96pub struct SiteId(pub u32);
97
98/// Process-local id of a runtime value.
99#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
100pub struct ValueId(pub u64);
101
102/// Tagged union over the stable id kinds a runtime entity may carry.
103#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
104pub enum RuntimeId {
105    /// A class id.
106    Class(ClassId),
107    /// A function id.
108    Function(FunctionId),
109    /// A macro id.
110    Macro(MacroId),
111    /// A shape id.
112    Shape(ShapeId),
113    /// A codec id.
114    Codec(CodecId),
115    /// A number-domain id.
116    NumberDomain(NumberDomainId),
117    /// An opaque site id.
118    Site(SiteId),
119    /// A plain runtime value with no registered id kind.
120    Value,
121}
122
123/// An optionally namespaced name for a runtime entity.
124///
125/// The kernel defines the symbol contract; it is the name that appears in
126/// [`Expr`](crate::expr::Expr) forms, refs, and registry keys. Names are
127/// reference-counted (`Arc<str>`), not interned: cloning is cheap, but there is
128/// no global dedup table, so two symbols with equal text are distinct
129/// allocations that merely compare equal.
130///
131/// # Name grammar
132///
133/// A symbol is a `namespace` (optional) plus a bare `name`. Bare names are
134/// treated as opaque text by the kernel; the qualified rendering is
135/// `namespace/name` (or just `name` when unqualified). [`as_qualified_str`] is
136/// therefore **not injective** when a component contains `/`:
137/// `Symbol::new("a/b")` and `Symbol::qualified("a", "b")` render identically yet
138/// are unequal. Construct symbols at untrusted boundaries with
139/// [`Symbol::checked`], which rejects `/`, NUL, and control characters so the
140/// qualified rendering stays unambiguous.
141///
142/// [`as_qualified_str`]: Symbol::as_qualified_str
143///
144/// # Examples
145///
146/// ```
147/// # use sim_kernel::id::Symbol;
148/// let plain = Symbol::new("car");
149/// assert_eq!(plain.as_qualified_str(), "car");
150///
151/// let qualified = Symbol::qualified("core", "Bool");
152/// assert_eq!(qualified.as_qualified_str(), "core/Bool");
153/// ```
154#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
155pub struct Symbol {
156    /// The optional namespace component (the part before `/`).
157    pub namespace: Option<Arc<str>>,
158    /// The bare name component.
159    pub name: Arc<str>,
160}
161
162impl Symbol {
163    /// Creates an unqualified symbol with no namespace.
164    pub fn new(name: impl Into<Arc<str>>) -> Self {
165        Self {
166            namespace: None,
167            name: name.into(),
168        }
169    }
170
171    /// Creates an unqualified symbol after validating the name.
172    ///
173    /// Unlike [`Symbol::new`], this rejects names that would make the qualified
174    /// rendering ambiguous or unprintable: names containing `/` (the
175    /// namespace separator), NUL, or any other control character. Codecs and
176    /// other untrusted boundaries should adopt this constructor; trusted kernel
177    /// call sites may keep using the infallible [`Symbol::new`].
178    pub fn checked(name: impl Into<Arc<str>>) -> Result<Self, SymbolError> {
179        let name = name.into();
180        validate_name(&name)?;
181        Ok(Self {
182            namespace: None,
183            name,
184        })
185    }
186
187    /// Creates a symbol qualified by `namespace`.
188    pub fn qualified(namespace: impl Into<Arc<str>>, name: impl Into<Arc<str>>) -> Self {
189        Self {
190            namespace: Some(namespace.into()),
191            name: name.into(),
192        }
193    }
194
195    /// Renders the symbol as `namespace/name`, or just `name` when unqualified.
196    pub fn as_qualified_str(&self) -> String {
197        match &self.namespace {
198            Some(namespace) => format!("{namespace}/{}", self.name),
199            None => self.name.to_string(),
200        }
201    }
202}
203
204/// Reason a name was rejected by [`Symbol::checked`].
205#[derive(Clone, Debug, PartialEq, Eq, Error)]
206pub enum SymbolError {
207    /// The name contained `/`, which is reserved as the namespace separator.
208    #[error("symbol name {0:?} contains the reserved namespace separator '/'")]
209    ContainsSeparator(String),
210    /// The name contained a NUL or other control character.
211    #[error("symbol name {0:?} contains a control character")]
212    ContainsControl(String),
213}
214
215fn validate_name(name: &str) -> Result<(), SymbolError> {
216    if name.contains('/') {
217        return Err(SymbolError::ContainsSeparator(name.to_owned()));
218    }
219    if name.chars().any(|ch| ch.is_control()) {
220        return Err(SymbolError::ContainsControl(name.to_owned()));
221    }
222    Ok(())
223}
224
225impl core::fmt::Display for Symbol {
226    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
227        f.write_str(&self.as_qualified_str())
228    }
229}
230
231#[cfg(test)]
232mod tests {
233    use super::*;
234
235    #[test]
236    fn core_class_ids_are_unique() {
237        let ids = [
238            CORE_CLASS_CLASS_ID,
239            CORE_NIL_CLASS_ID,
240            CORE_BOOL_CLASS_ID,
241            CORE_NUMBER_CLASS_ID,
242            CORE_SYMBOL_CLASS_ID,
243            CORE_STRING_CLASS_ID,
244            CORE_BYTES_CLASS_ID,
245            CORE_LIST_CLASS_ID,
246            CORE_TABLE_CLASS_ID,
247            CORE_EXPR_CLASS_ID,
248            CORE_FUNCTION_CLASS_ID,
249            CORE_SHAPE_CLASS_ID,
250            CORE_THUNK_CLASS_ID,
251            CORE_EVAL_REQUEST_CLASS_ID,
252            CORE_EVAL_REPLY_CLASS_ID,
253            CORE_MACRO_CLASS_ID,
254            CORE_SHAPE_MATCH_CLASS_ID,
255            CORE_CODEC_CLASS_ID,
256            CORE_HELP_CLASS_ID,
257            CORE_TEST_CLASS_ID,
258            CORE_NUMBER_DOMAIN_CLASS_ID,
259            CORE_LOCAL_EVAL_FABRIC_CLASS_ID,
260            CORE_SEQUENCE_CLASS_ID,
261            CORE_CARD_CLASS_ID,
262        ];
263        let unique = ids
264            .iter()
265            .copied()
266            .collect::<std::collections::BTreeSet<_>>();
267        assert_eq!(ids.len(), unique.len());
268    }
269
270    #[test]
271    fn checked_accepts_a_plain_name() {
272        let symbol = Symbol::checked("car").expect("plain name is valid");
273        assert_eq!(symbol, Symbol::new("car"));
274    }
275
276    #[test]
277    fn checked_rejects_the_namespace_separator() {
278        assert_eq!(
279            Symbol::checked("a/b"),
280            Err(SymbolError::ContainsSeparator("a/b".to_owned()))
281        );
282    }
283
284    #[test]
285    fn checked_rejects_nul_and_control_characters() {
286        assert_eq!(
287            Symbol::checked("a\0b"),
288            Err(SymbolError::ContainsControl("a\0b".to_owned()))
289        );
290        assert_eq!(
291            Symbol::checked("a\tb"),
292            Err(SymbolError::ContainsControl("a\tb".to_owned()))
293        );
294    }
295}