Skip to main content

geop_ops_model/
names.rs

1//! [`NameRegistry`]: the two-way mapping between an entity and its name that
2//! [`super::NamedModel`] keeps in sync with its topology, and [`Namer`],
3//! which builds those names.
4
5use imbl::HashMap;
6
7use geop_core_math::geop_error::{GeopError, GeopResult};
8use geop_core_program::program::validate_operation_id;
9
10use super::ids::RefId;
11
12/// Every entity's name, by its id ([`NameRegistry::entries`]).
13pub(crate) type Names = Vec<(RefId, String)>;
14
15/// Every alias, with the name it stands for.
16pub(crate) type Aliases = Vec<(String, String)>;
17
18/// A two-way `RefId <-> String` mapping. Every entity a
19/// [`super::NamedModel`] exposes — a vertex, edge, face or solid — has
20/// exactly one live entry here for as long as it exists. Its maps share
21/// their structure with their copies, as the topology's do.
22///
23/// Names are chosen by whoever creates the entity, following the scheme in
24/// the crate docs, never generated here from a counter: a counter depends on
25/// creation order, which is exactly what a name must not depend on.
26#[derive(Clone, Debug, Default, PartialEq)]
27pub struct NameRegistry {
28    id_to_name: HashMap<RefId, String>,
29    name_to_id: HashMap<String, RefId>,
30    /// Names of entities made part of another, each with that other's
31    /// name: they resolve to it (see [`NameRegistry::alias`]).
32    aliases: HashMap<String, String>,
33}
34
35impl NameRegistry {
36    pub fn new() -> Self {
37        Self::default()
38    }
39
40    /// Registers `id` under `name`. Fails if `id` is already registered
41    /// (under any name) or if `name` is already taken by a different id.
42    pub fn insert(&mut self, id: impl Into<RefId>, name: impl Into<String>) -> GeopResult<()> {
43        let id = id.into();
44        let name = name.into();
45        if let Some(existing) = self.id_to_name.get(&id) {
46            return Err(GeopError::new(format!(
47                "NameRegistry::insert: {id} is already named {existing:?}, cannot also name it {name:?}"
48            )));
49        }
50        if let Some(&existing) = self.name_to_id.get(&name) {
51            return Err(GeopError::new(format!(
52                "NameRegistry::insert: name {name:?} is already used by {existing}"
53            )));
54        }
55        self.aliases.remove(&name);
56        self.id_to_name.insert(id, name.clone());
57        self.name_to_id.insert(name, id);
58        Ok(())
59    }
60
61    /// Lets `name`, the name of an entity made part of the entity named
62    /// `into` — two faces on one surface made one, two edges joined —
63    /// stand for `into` from now on, so what referred to either still
64    /// finds it. Fails if `name` names an entity of its own.
65    pub fn alias(&mut self, name: impl Into<String>, into: impl Into<String>) -> GeopResult<()> {
66        let name = name.into();
67        if let Some(&id) = self.name_to_id.get(&name) {
68            return Err(GeopError::new(format!(
69                "NameRegistry::alias: {name:?} names {id}, it cannot stand for another"
70            )));
71        }
72        let into = into.into();
73        // What stood for `name` stands for `into` now.
74        let stood: Vec<String> = (self.aliases.iter())
75            .filter(|(_, target)| **target == name)
76            .map(|(alias, _)| alias.clone())
77            .collect();
78        for alias in stood {
79            self.aliases.insert(alias, into.clone());
80        }
81        self.aliases.insert(name, into);
82        Ok(())
83    }
84
85    /// What else stands for the entity `name` names (see [`Self::alias`]), in
86    /// order.
87    pub fn aliases_of(&self, name: &str) -> Vec<String> {
88        let mut aliases: Vec<String> = (self.aliases.iter())
89            .filter(|(_, target)| *target == name)
90            .map(|(alias, _)| alias.clone())
91            .collect();
92        aliases.sort();
93        aliases
94    }
95
96    /// Gives the already named `id` the name `new_name` instead.
97    ///
98    /// For an operation that can only tell what an entity should be called
99    /// once it has finished — a boolean numbers the crossings of two edges
100    /// along one of them, which it knows only after finding all of them. It
101    /// names such entities provisionally while it runs and settles every
102    /// name before it returns; nothing outside the operation ever sees a
103    /// provisional one.
104    pub fn rename(&mut self, id: impl Into<RefId>, new_name: impl Into<String>) -> GeopResult<()> {
105        let id = id.into();
106        let new_name = new_name.into();
107        let old = self.id_to_name.get(&id).cloned().ok_or_else(|| {
108            GeopError::new(format!("NameRegistry::rename: {id} has no name to change"))
109        })?;
110        if old == new_name {
111            return Ok(());
112        }
113        if let Some(&existing) = self.name_to_id.get(&new_name) {
114            return Err(GeopError::new(format!(
115                "NameRegistry::rename: cannot rename {id} from {old:?} to {new_name:?}, which is already used by {existing}"
116            )));
117        }
118        self.name_to_id.remove(&old);
119        self.id_to_name.insert(id, new_name.clone());
120        self.name_to_id.insert(new_name, id);
121        Ok(())
122    }
123
124    /// Forgets `id` and its name. A no-op if `id` was never registered.
125    pub fn remove(&mut self, id: impl Into<RefId>) {
126        if let Some(name) = self.id_to_name.remove(&id.into()) {
127            self.name_to_id.remove(&name);
128        }
129    }
130
131    pub fn name_of(&self, id: impl Into<RefId>) -> Option<&str> {
132        self.id_to_name.get(&id.into()).map(String::as_str)
133    }
134
135    /// Whether `name` is an entity's own name — not merely an alias, which
136    /// a new entity may take over.
137    pub fn is_taken(&self, name: &str) -> bool {
138        self.name_to_id.contains_key(name)
139    }
140
141    /// The entity named `name`, or that `name` stands for as an alias.
142    pub fn id_of(&self, name: &str) -> Option<RefId> {
143        (self.name_to_id.get(name).copied())
144            .or_else(|| self.name_to_id.get(self.aliases.get(name)?).copied())
145    }
146
147    /// Every `(id, name)`, sorted by the kind of entity and its id, and
148    /// every alias with the name it stands for, sorted: what a model kept
149    /// between processes writes of its names ([`NameRegistry::from_entries`]).
150    pub(crate) fn entries(&self) -> (Names, Aliases) {
151        let mut names: Vec<(RefId, String)> = (self.id_to_name.iter())
152            .map(|(&id, name)| (id, name.clone()))
153            .collect();
154        names.sort_by_key(|(id, _)| crate::kept::ref_id(id));
155        let mut aliases: Vec<(String, String)> = (self.aliases.iter())
156            .map(|(name, into)| (name.clone(), into.clone()))
157            .collect();
158        aliases.sort();
159        (names, aliases)
160    }
161
162    /// The registry of the `names` and `aliases` [`NameRegistry::entries`]
163    /// gave.
164    pub(crate) fn from_entries(names: Names, aliases: Aliases) -> Self {
165        Self {
166            name_to_id: (names.iter())
167                .map(|(id, name)| (name.clone(), *id))
168                .collect(),
169            id_to_name: names.into_iter().collect(),
170            aliases: aliases.into_iter().collect(),
171        }
172    }
173
174    /// Every `(id, name)`, in no particular order.
175    pub fn iter(&self) -> impl Iterator<Item = (RefId, &str)> {
176        self.id_to_name
177            .iter()
178            .map(|(&id, name)| (id, name.as_str()))
179    }
180}
181
182/// The id of the step that named the entity `name` (see [`Namer`]): `E`
183/// for `extrude(E,end)`. None for a name no step built.
184pub fn operation_of(name: &str) -> Option<&str> {
185    let (_, rest) = name.split_once('(')?;
186    let end = rest.find([',', ')'])?;
187    Some(&rest[..end])
188}
189
190/// Builds the names one run of one operation gives to what it creates:
191/// `kind(operation,arg,...)`, see the crate docs.
192#[derive(Clone, Debug, PartialEq, Eq)]
193pub struct Namer {
194    kind: String,
195    operation: String,
196    /// The arguments every name starts with, see [`Namer::scoped`].
197    scope: Vec<String>,
198}
199
200impl Namer {
201    /// Names for operation `kind` run as the program step `operation`.
202    pub fn new(kind: &str, operation: &str) -> GeopResult<Self> {
203        validate_operation_id(operation)?;
204        Ok(Self {
205            kind: kind.to_string(),
206            operation: operation.to_string(),
207            scope: Vec::new(),
208        })
209    }
210
211    /// The names for one of several parts of the operation's work that
212    /// would otherwise name things alike — the second side of an extrude
213    /// built on its own, say: `kind(operation,scope,arg,...)`.
214    pub fn scoped(&self, scope: &str) -> Self {
215        let mut scoped = self.clone();
216        scoped.scope.push(scope.to_string());
217        scoped
218    }
219
220    /// `kind(operation)`: the operation's own name, which is what the solid
221    /// it builds is called — `kind(operation,scope)` for a scoped one.
222    pub fn root(&self) -> String {
223        self.name(&[])
224    }
225
226    /// `kind(operation,arg,...)`, an argument longer than
227    /// [`LONGEST_ARGUMENT`] given by its digest instead (see [`digest`]).
228    pub fn name(&self, args: &[&str]) -> String {
229        let mut name = format!("{}({}", self.kind, self.operation);
230        for arg in self
231            .scope
232            .iter()
233            .map(String::as_str)
234            .chain(args.iter().copied())
235        {
236            name.push(',');
237            if arg.len() > LONGEST_ARGUMENT {
238                name.push_str(&digest(arg));
239            } else {
240                name.push_str(arg);
241            }
242        }
243        name.push(')');
244        name
245    }
246
247    /// What follows the operation in `name`, one of this namer's: `X` for
248    /// `kind(operation,X)`. None, for a name of another.
249    pub fn arguments_of<'a>(&self, name: &'a str) -> Option<&'a str> {
250        let root = self.root();
251        name.strip_prefix(root.strip_suffix(')')?)?
252            .strip_prefix(',')?
253            .strip_suffix(')')
254    }
255
256    /// `kind(operation,origin,at)`: a piece split off the entity named
257    /// `origin` at — or along — the entity named `at`. A piece is named
258    /// after what it was split off first of all, so that [`origin_of`]
259    /// traces it back however often it was split since.
260    pub fn piece(&self, origin: &str, at: &str) -> String {
261        self.name(&[origin, at])
262    }
263
264    /// Whether `name` is one of the [`Namer::piece`]s this namer named of
265    /// `origin`.
266    pub fn is_piece_of(&self, name: &str, origin: &str) -> bool {
267        // `kind(operation,origin,` — a piece's name without what it was
268        // split at, and its `)`.
269        let mut prefix = self.piece(origin, "");
270        prefix.pop();
271        name.starts_with(&prefix)
272    }
273}
274
275/// What the entity named `name` was split off (see [`Namer::piece`]): the
276/// first of the names among its arguments — an argument that is a name
277/// itself, `kind(...)`, or a long one's [`digest`]. None, if it has none.
278pub fn origin_of(name: &str) -> Option<&str> {
279    let open = name.find('(')?;
280    let inner = name[open + 1..].strip_suffix(')')?;
281    let mut depth = 0usize;
282    let mut start = 0usize;
283    // The first argument is the step's id, never a name.
284    let mut first = true;
285    for (k, c) in inner
286        .char_indices()
287        .chain(std::iter::once((inner.len(), ',')))
288    {
289        match c {
290            '(' => depth += 1,
291            ')' => depth = depth.saturating_sub(1),
292            ',' if depth == 0 => {
293                let argument = &inner[start..k];
294                if !first && (argument.contains('(') || argument.starts_with('#')) {
295                    return Some(argument);
296                }
297                first = false;
298                start = k + 1;
299            }
300            _ => {}
301        }
302    }
303    None
304}
305
306/// The longest argument a name spells out (see [`Namer::name`]).
307///
308/// Names are built from names, and some of them repeat one: a boolean names
309/// the piece of an edge it splits after the edge and the vertex it starts
310/// at, and that vertex after the edge again. Cutting the same edge again
311/// and again — the teeth of a gear, one gap at a time — so doubles its
312/// name's length with every cut, and twenty of them made names of tens of
313/// megabytes.
314pub const LONGEST_ARGUMENT: usize = 160;
315
316/// A long argument of a name, as the name spells it: `#` and 32 hex
317/// digits, its digest ([`geop_core_program::cache::digest`]). A hash
318/// rather than a counter, so that a name still depends only on what the
319/// entity was made from, and one that does not change between Rust
320/// versions, so that a saved program's names keep meaning the same
321/// entities.
322pub fn digest(text: &str) -> String {
323    format!("#{:032x}", geop_core_program::cache::digest(text))
324}
325
326#[cfg(test)]
327mod tests {
328    use super::*;
329
330    /// A long argument is spelled by its digest: the name stays short, and
331    /// the digest depends on the argument alone.
332    #[test]
333    fn long_arguments_are_digested() {
334        let namer = Namer::new("combine", "teeth").unwrap();
335        let long = "x".repeat(LONGEST_ARGUMENT + 1);
336        let name = namer.name(&["e1", &long]);
337        assert_eq!(name, format!("combine(teeth,e1,{})", digest(&long)));
338        assert_eq!(digest(&long).len(), 33);
339        assert_ne!(digest(&long), digest(&"x".repeat(LONGEST_ARGUMENT + 2)));
340        // Fixed for good: saved programs refer to entities by these names.
341        assert_eq!(digest(""), "#6c62272e07bb014262b821756295c58d");
342        let short = "x".repeat(LONGEST_ARGUMENT);
343        assert_eq!(namer.name(&[&short]), format!("combine(teeth,{short})"));
344    }
345
346    #[test]
347    fn origin_is_the_first_name_among_the_arguments() {
348        assert_eq!(
349            origin_of(
350                "combine(holes,3,linear_pattern(holes,3,extrude(pin,c1)),extrude(plate,end))"
351            ),
352            Some("linear_pattern(holes,3,extrude(pin,c1))")
353        );
354        assert_eq!(origin_of("combine(e,#0123,x)"), Some("#0123"));
355        assert_eq!(origin_of("extrude(plate,end)"), None);
356        assert_eq!(origin_of("extrude(plate)"), None);
357    }
358
359    /// A piece is named after its origin, which [`origin_of`] finds again,
360    /// and which [`Namer::is_piece_of`] tells, as the namer named it.
361    #[test]
362    fn a_piece_is_named_after_its_origin() {
363        let namer = Namer::new("combine", "cut").unwrap().scoped("2");
364        let piece = namer.piece("extrude(box,end)", "combine(cut,2,x(a),y(b),0,1)");
365        assert_eq!(
366            piece,
367            "combine(cut,2,extrude(box,end),combine(cut,2,x(a),y(b),0,1))"
368        );
369        assert_eq!(origin_of(&piece), Some("extrude(box,end)"));
370        assert!(namer.is_piece_of(&piece, "extrude(box,end)"));
371        assert!(!namer.is_piece_of(&piece, "extrude(box)"));
372        assert!(!namer.scoped("3").is_piece_of(&piece, "extrude(box,end)"));
373    }
374}