Skip to main content

telltale_machine/
intern.rs

1//! String interning for hot runtime paths.
2
3use crate::session::SessionId;
4use serde::{Deserialize, Serialize};
5use std::collections::BTreeMap;
6
7/// Stable identifier for interned runtime strings.
8pub type StringId = u32;
9
10/// Stable identifier for interned runtime edges.
11pub type EdgeId = u32;
12
13/// Runtime symbol table used to intern repeated role/label strings.
14#[derive(Debug, Clone, Default, Serialize, Deserialize)]
15pub struct SymbolTable {
16    symbols: Vec<String>,
17    index: BTreeMap<String, StringId>,
18}
19
20impl SymbolTable {
21    /// Create an empty symbol table.
22    #[must_use]
23    pub fn new() -> Self {
24        Self::default()
25    }
26
27    /// Intern a string and return its stable id.
28    pub fn intern(&mut self, value: &str) -> StringId {
29        if let Some(id) = self.index.get(value) {
30            return *id;
31        }
32        let id = match u32::try_from(self.symbols.len()) {
33            Ok(id) => id,
34            Err(_) => return u32::MAX,
35        };
36        let owned = value.to_string();
37        self.symbols.push(owned.clone());
38        self.index.insert(owned, id);
39        id
40    }
41
42    /// Resolve an id to a string, if present.
43    #[must_use]
44    #[allow(clippy::as_conversions)]
45    pub fn resolve(&self, id: StringId) -> Option<&str> {
46        // u32 -> usize is always safe on 32-bit or larger platforms
47        self.symbols.get(id as usize).map(String::as_str)
48    }
49
50    /// Number of interned symbols.
51    #[must_use]
52    pub fn len(&self) -> usize {
53        self.symbols.len()
54    }
55
56    /// Whether the table is empty.
57    #[must_use]
58    pub fn is_empty(&self) -> bool {
59        self.symbols.is_empty()
60    }
61}
62
63/// Deterministic key for an interned session edge.
64#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
65pub struct EdgeSymbol {
66    /// Session containing the edge.
67    pub sid: SessionId,
68    /// Interned sender role id.
69    pub sender: StringId,
70    /// Interned receiver role id.
71    pub receiver: StringId,
72}
73
74/// Runtime symbol table used to intern repeated session edges.
75#[derive(Debug, Clone, Default, Serialize, Deserialize)]
76pub struct EdgeSymbolTable {
77    symbols: Vec<EdgeSymbol>,
78    index: BTreeMap<EdgeSymbol, EdgeId>,
79}
80
81impl EdgeSymbolTable {
82    /// Create an empty edge symbol table.
83    #[must_use]
84    pub fn new() -> Self {
85        Self::default()
86    }
87
88    /// Intern a session edge and return its stable id.
89    pub fn intern(&mut self, sid: SessionId, sender: StringId, receiver: StringId) -> EdgeId {
90        let edge = EdgeSymbol {
91            sid,
92            sender,
93            receiver,
94        };
95        if let Some(id) = self.index.get(&edge) {
96            return *id;
97        }
98        let id = match u32::try_from(self.symbols.len()) {
99            Ok(id) => id,
100            Err(_) => return u32::MAX,
101        };
102        self.symbols.push(edge);
103        self.index.insert(edge, id);
104        id
105    }
106
107    /// Resolve an id to its edge components, if present.
108    #[must_use]
109    #[allow(clippy::as_conversions)]
110    pub fn resolve(&self, id: EdgeId) -> Option<EdgeSymbol> {
111        self.symbols.get(id as usize).copied()
112    }
113
114    /// Number of interned edges.
115    #[must_use]
116    pub fn len(&self) -> usize {
117        self.symbols.len()
118    }
119
120    /// Whether the table is empty.
121    #[must_use]
122    pub fn is_empty(&self) -> bool {
123        self.symbols.is_empty()
124    }
125}