1use std::collections::HashMap;
6
7use geop_core_math::geop_error::{GeopError, GeopResult};
8
9use crate::ids::RefId;
10
11#[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 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 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 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 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 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
106pub 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#[derive(Clone, Debug)]
127pub struct Namer {
128 kind: String,
129 operation: String,
130}
131
132impl Namer {
133 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 pub fn root(&self) -> String {
145 format!("{}({})", self.kind, self.operation)
146 }
147
148 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}