Skip to main content

geop_core_part/
names.rs

1//! [`NameRegistry`]: the two-way mapping between an entity and its name that
2//! [`crate::Part`] keeps in sync with its topology and sketches, and
3//! [`Namer`], which builds those names.
4
5use std::collections::HashMap;
6
7use geop_core_math::geop_error::{GeopError, GeopResult};
8
9use crate::ids::RefId;
10
11/// A two-way `RefId <-> String` mapping. Every entity a [`crate::Part`]
12/// exposes — a vertex, edge, face, solid or sketch — has exactly one live
13/// entry here for as long as it exists.
14///
15/// Names are chosen by whoever creates the entity, following the scheme in
16/// the crate docs, never generated here from a counter: a counter depends on
17/// creation order, which is exactly what a name must not depend on.
18#[derive(Clone, Debug, Default)]
19pub struct NameRegistry {
20    id_to_name: HashMap<RefId, String>,
21    name_to_id: HashMap<String, RefId>,
22}
23
24impl NameRegistry {
25    pub fn new() -> Self {
26        Self::default()
27    }
28
29    /// Registers `id` under `name`. Fails if `id` is already registered
30    /// (under any name) or if `name` is already taken by a different id.
31    pub fn insert(&mut self, id: impl Into<RefId>, name: impl Into<String>) -> GeopResult<()> {
32        let id = id.into();
33        let name = name.into();
34        if let Some(existing) = self.id_to_name.get(&id) {
35            return Err(GeopError::new(format!(
36                "NameRegistry::insert: {id} is already named {existing:?}, cannot also name it {name:?}"
37            )));
38        }
39        if let Some(&existing) = self.name_to_id.get(&name) {
40            return Err(GeopError::new(format!(
41                "NameRegistry::insert: name {name:?} is already used by {existing}"
42            )));
43        }
44        self.id_to_name.insert(id, name.clone());
45        self.name_to_id.insert(name, id);
46        Ok(())
47    }
48
49    /// Gives the already named `id` the name `new_name` instead.
50    ///
51    /// For an operation that can only tell what an entity should be called
52    /// once it has finished — a boolean numbers the crossings of two edges
53    /// along one of them, which it knows only after finding all of them. It
54    /// names such entities provisionally while it runs and settles every
55    /// name before it returns; nothing outside the operation ever sees a
56    /// provisional one.
57    pub fn rename(&mut self, id: impl Into<RefId>, new_name: impl Into<String>) -> GeopResult<()> {
58        let id = id.into();
59        let new_name = new_name.into();
60        let old = self.id_to_name.get(&id).cloned().ok_or_else(|| {
61            GeopError::new(format!("NameRegistry::rename: {id} has no name to change"))
62        })?;
63        if old == new_name {
64            return Ok(());
65        }
66        if let Some(&existing) = self.name_to_id.get(&new_name) {
67            return Err(GeopError::new(format!(
68                "NameRegistry::rename: cannot rename {id} from {old:?} to {new_name:?}, which is already used by {existing}"
69            )));
70        }
71        self.name_to_id.remove(&old);
72        self.id_to_name.insert(id, new_name.clone());
73        self.name_to_id.insert(new_name, id);
74        Ok(())
75    }
76
77    /// Forgets `id` and its name. A no-op if `id` was never registered.
78    pub fn remove(&mut self, id: impl Into<RefId>) {
79        if let Some(name) = self.id_to_name.remove(&id.into()) {
80            self.name_to_id.remove(&name);
81        }
82    }
83
84    /// Forgets every entry whose id `alive` rejects.
85    pub fn retain(&mut self, mut alive: impl FnMut(RefId) -> bool) {
86        self.id_to_name.retain(|&id, _| alive(id));
87        self.name_to_id.retain(|_, id| alive(*id));
88    }
89
90    pub fn name_of(&self, id: impl Into<RefId>) -> Option<&str> {
91        self.id_to_name.get(&id.into()).map(String::as_str)
92    }
93
94    pub fn id_of(&self, name: &str) -> Option<RefId> {
95        self.name_to_id.get(name).copied()
96    }
97
98    /// Every `(id, name)`, in no particular order.
99    pub fn iter(&self) -> impl Iterator<Item = (RefId, &str)> {
100        self.id_to_name
101            .iter()
102            .map(|(&id, name)| (id, name.as_str()))
103    }
104}
105
106/// Checks that `id` can be an operation id: non-empty, and only ASCII
107/// letters, digits, `_`, `-` and `.`. Nothing that could be mistaken for the
108/// `(`, `)` and `,` that structure a name, so the names built from it stay
109/// unambiguous.
110pub fn validate_operation_id(id: &str) -> GeopResult<()> {
111    if !id.is_empty()
112        && id
113            .chars()
114            .all(|c| c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '.'))
115    {
116        Ok(())
117    } else {
118        Err(GeopError::new(format!(
119            "{id:?} is not a valid operation id: use only ASCII letters, digits, '_', '-' and '.'"
120        )))
121    }
122}
123
124/// Builds the names one run of one operation gives to what it creates:
125/// `kind(operation,arg,...)`, see the crate docs.
126#[derive(Clone, Debug)]
127pub struct Namer {
128    kind: String,
129    operation: String,
130}
131
132impl Namer {
133    /// Names for operation `kind` run as the program step `operation`.
134    pub fn new(kind: &str, operation: &str) -> GeopResult<Self> {
135        validate_operation_id(operation)?;
136        Ok(Self {
137            kind: kind.to_string(),
138            operation: operation.to_string(),
139        })
140    }
141
142    /// `kind(operation)`: the operation's own name, which is what the solid
143    /// it builds is called.
144    pub fn root(&self) -> String {
145        format!("{}({})", self.kind, self.operation)
146    }
147
148    /// `kind(operation,arg,...)`.
149    pub fn name(&self, args: &[&str]) -> String {
150        let mut name = format!("{}({}", self.kind, self.operation);
151        for arg in args {
152            name.push(',');
153            name.push_str(arg);
154        }
155        name.push(')');
156        name
157    }
158}