1use 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
12pub(crate) type Names = Vec<(RefId, String)>;
14
15pub(crate) type Aliases = Vec<(String, String)>;
17
18#[derive(Clone, Debug, Default, PartialEq)]
27pub struct NameRegistry {
28 id_to_name: HashMap<RefId, String>,
29 name_to_id: HashMap<String, RefId>,
30 aliases: HashMap<String, String>,
33}
34
35impl NameRegistry {
36 pub fn new() -> Self {
37 Self::default()
38 }
39
40 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 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 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 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 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 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 pub fn is_taken(&self, name: &str) -> bool {
138 self.name_to_id.contains_key(name)
139 }
140
141 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 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 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 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
182pub 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#[derive(Clone, Debug, PartialEq, Eq)]
193pub struct Namer {
194 kind: String,
195 operation: String,
196 scope: Vec<String>,
198}
199
200impl Namer {
201 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 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 pub fn root(&self) -> String {
223 self.name(&[])
224 }
225
226 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 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 pub fn piece(&self, origin: &str, at: &str) -> String {
261 self.name(&[origin, at])
262 }
263
264 pub fn is_piece_of(&self, name: &str, origin: &str) -> bool {
267 let mut prefix = self.piece(origin, "");
270 prefix.pop();
271 name.starts_with(&prefix)
272 }
273}
274
275pub 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 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
306pub const LONGEST_ARGUMENT: usize = 160;
315
316pub 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 #[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 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 #[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}