1use std::collections::HashMap;
13use std::fs;
14use std::path::{Path, PathBuf};
15
16use crate::model::{AssemblyModel, RegisteredModel, RegistrationModel, TypeModel};
17
18const MAX_HOPS: usize = 8;
21
22pub struct ModelSet {
24 models: Vec<AssemblyModel>,
25 exports: HashMap<String, String>,
27 types: HashMap<String, (usize, usize)>,
29 functions: HashMap<String, (usize, usize, usize)>,
33 aliases: HashMap<String, String>,
35}
36
37impl ModelSet {
38 pub fn new(models: Vec<AssemblyModel>) -> Self {
40 let mut exports = HashMap::new();
41 for model in &models {
42 for export in &model.exports {
43 exports.entry(export.path.clone()).or_insert_with(|| export.declared.clone());
44 }
45 }
46 let mut set = Self { models, exports, types: HashMap::new(), functions: HashMap::new(), aliases: HashMap::new() };
47 let mut types = HashMap::new();
48 let mut functions = HashMap::new();
49 let mut aliases = HashMap::new();
50 for (model_index, model) in set.models.iter().enumerate() {
51 for (type_index, type_) in model.types.iter().enumerate() {
52 types.entry(set.canonical(&type_.rust_path.text)).or_insert((model_index, type_index));
53 for (position, registered) in type_.registered.iter().enumerate() {
54 if let Some(function_of) = ®istered.function_of {
55 let path = format!("{}::{}", set.canonical_path(function_of), registered.accessor);
56 functions.entry(path).or_insert((model_index, type_index, position));
57 }
58 }
59 }
60 for alias in model.aliases.iter().filter(|alias| alias.target.is_resolved()) {
62 aliases.entry(set.canonical_path(&alias.path)).or_insert_with(|| set.canonical(&alias.target.text));
63 }
64 }
65 set.types = types;
66 set.functions = functions;
67 set.aliases = aliases;
68 set
69 }
70
71 pub fn models(&self) -> &[AssemblyModel] {
72 &self.models
73 }
74
75 pub fn is_empty(&self) -> bool {
76 self.models.is_empty()
77 }
78
79 pub fn type_at(&self, position: (usize, usize)) -> &TypeModel {
81 &self.models[position.0].types[position.1]
82 }
83
84 pub fn canonical_path(&self, path: &str) -> String {
88 let mut current = path.to_string();
89 for _ in 0..MAX_HOPS {
90 match self.declared(¤t) {
91 Some(next) if next != current => current = next,
92 _ => break,
93 }
94 }
95 current
96 }
97
98 fn declared(&self, path: &str) -> Option<String> {
99 let mut end = path.len();
100 while end > 0 {
101 if let Some(declared) = self.exports.get(&path[..end]) {
102 return Some(format!("{declared}{}", &path[end..]));
103 }
104 end = path[..end].rfind("::")?;
105 }
106 None
107 }
108
109 pub fn canonical(&self, text: &str) -> String {
113 if self.exports.is_empty() {
114 return text.to_string();
115 }
116 self.map_paths(text, &|path| self.canonical_path(path))
117 }
118
119 pub fn expanded(&self, text: &str) -> String {
124 let mut current = self.canonical(text);
125 if self.aliases.is_empty() {
126 return current;
127 }
128 for _ in 0..MAX_HOPS {
129 let next = self.map_paths(¤t, &|path| self.aliases.get(path).cloned().unwrap_or_else(|| path.to_string()));
130 if next == current {
131 break;
132 }
133 current = next;
134 }
135 current
136 }
137
138 fn map_paths(&self, text: &str, map: &dyn Fn(&str) -> String) -> String {
140 let is_word = |character: char| character.is_alphanumeric() || character == '_';
141 let mut result = String::with_capacity(text.len());
142 let mut rest = text;
143 let mut before: Option<char> = None;
146 while !rest.is_empty() {
147 let starts = rest.starts_with("::") && !before.is_some_and(|character| is_word(character) || character == '>');
148 if starts {
149 let mut end = 0;
150 loop {
151 let tail = &rest[end..];
152 let Some(name) = tail.strip_prefix("::") else { break };
153 let length: usize = name.chars().take_while(|character| is_word(*character)).map(char::len_utf8).sum();
154 if length == 0 {
155 break;
156 }
157 end += 2 + length;
158 }
159 if end > 0 {
160 result.push_str(&map(&rest[..end]));
161 before = rest[..end].chars().next_back();
162 rest = &rest[end..];
163 continue;
164 }
165 }
166 let Some(character) = rest.chars().next() else { break };
167 result.push(character);
168 before = Some(character);
169 rest = &rest[character.len_utf8()..];
170 }
171 result
172 }
173
174 pub fn position_of_rust_type(&self, rust_path: &str) -> Option<(usize, usize)> {
177 if let Some(found) = self.types.get(rust_path) {
178 return Some(*found);
179 }
180 self.types.get(&self.canonical(rust_path)).copied()
181 }
182
183 pub fn find_rust_type(&self, rust_path: &str) -> Option<(&AssemblyModel, &TypeModel)> {
186 let (model, type_) = self.position_of_rust_type(rust_path)?;
187 Some((&self.models[model], &self.models[model].types[type_]))
188 }
189
190 pub fn find_type(&self, full_name: &str) -> Option<(&AssemblyModel, &TypeModel)> {
192 self.models.iter().find_map(|model| model.find_type(full_name).map(|type_| (model, type_)))
193 }
194
195 pub fn find_accessor(&self, path: &str) -> Option<(&TypeModel, &RegisteredModel)> {
203 let path = self.canonical_path(path);
204 if let Some((model, type_, position)) = self.functions.get(&path) {
205 let type_ = self.type_at((*model, *type_));
206 return Some((type_, &type_.registered[*position]));
207 }
208 let (type_path, accessor) = path.rsplit_once("::")?;
209 let type_ = self.type_at(*self.types.get(type_path)?);
210 type_.registered.iter().find(|registered| registered.accessor == accessor && registered.function_of.is_none()).map(|registered| (type_, registered))
211 }
212
213 pub fn accessor_type_path<'a>(&'a self, listed: &'a TypeModel, registered: &'a RegisteredModel) -> &'a str {
216 match ®istered.function_of {
217 Some(function_of) => self.find_rust_type(function_of).map_or(function_of.as_str(), |(_, type_)| type_.named_path()),
218 None => listed.named_path(),
219 }
220 }
221
222 pub fn declaration_of<'a>(&'a self, registered: &'a RegisteredModel) -> Option<&'a RegisteredModel> {
226 let mut current = registered;
227 for _ in 0..MAX_HOPS {
228 if current.registration == RegistrationModel::Declared {
229 return Some(current);
230 }
231 let source = current.source.as_ref()?.resolved.as_deref()?;
232 current = self.find_accessor(source)?.1;
233 }
234 None
235 }
236
237 pub fn name_of<'a>(&'a self, registered: &'a RegisteredModel) -> Option<&'a str> {
240 match ®istered.name {
241 Some(name) => Some(name.as_str()),
242 None => self.declaration_of(registered)?.name.as_deref(),
243 }
244 }
245
246 pub fn exported_names<'a>(&'a self, module: &'a str) -> impl Iterator<Item = (&'a str, &'a str)> + 'a {
250 self.exports.iter().filter_map(move |(path, declared)| {
251 let name = path.strip_prefix(module)?.strip_prefix("::")?;
252 (!name.contains("::")).then_some((name, declared.as_str()))
253 })
254 }
255
256 pub fn read(paths: &[PathBuf]) -> Result<(Vec<AssemblyModel>, Vec<PathBuf>), String> {
261 let mut models: Vec<AssemblyModel> = Vec::new();
262 let mut files = Vec::new();
263 let mut pending: Vec<PathBuf> = paths.iter().rev().cloned().collect();
264 while let Some(path) = pending.pop() {
265 if files.contains(&path) {
266 continue;
267 }
268 let text = fs::read_to_string(&path).map_err(|error| format!("{}: {error}", path.display()))?;
269 let model = AssemblyModel::parse(&text).map_err(|error| format!("{}: {error}", path.display()))?;
270 let directory = path.parent().map(Path::to_path_buf).unwrap_or_default();
271 files.push(path);
272 if models.iter().any(|known| known.name == model.name) {
273 continue;
274 }
275 for dependency in model.dependencies.iter().rev() {
276 pending.push(directory.join(dependency));
277 }
278 models.push(model);
279 }
280 Ok((models, files))
281 }
282}
283
284#[cfg(test)]
285mod tests {
286 use super::*;
287 use crate::model::{AliasModel, CallableModel, ExportModel, RegisteredKind, RustType, TypeKind};
288
289 fn export(path: &str, declared: &str) -> ExportModel {
290 ExportModel { path: path.to_string(), declared: declared.to_string() }
291 }
292
293 fn registered(name: Option<&str>, accessor: &str, registration: RegistrationModel, source: Option<&str>) -> RegisteredModel {
294 RegisteredModel {
295 name: name.map(str::to_string),
296 kind: RegisteredKind::Styled,
297 value_type: RustType::resolved("f64"),
298 owner: None,
299 host: None,
300 accessor: accessor.to_string(),
301 function_of: None,
302 visibility: "pub".to_string(),
303 registration,
304 source: source.map(|path| CallableModel { path: Some(path.to_string()), resolved: Some(path.to_string()), dereferenced: None }),
305 assign_binding: false,
306 inherits: false,
307 read_only: false,
308 added_owners: Vec::new(),
309 }
310 }
311
312 fn set() -> ModelSet {
315 let mut base = AssemblyModel::new("Base", "base");
316 base.exports = vec![
317 export("::base::Shape", "::base::shapes::shape::Shape"),
318 export("::base::shapes::Shape", "::base::shapes::shape::Shape"),
319 export("::base::Handle", "::other::rc::Rc"),
320 export("::base::media::Brush", "::base::media::Brush"),
321 export("::base::Pen", "::base::media::pen::Pen"),
322 export("::base::Shapes", "::base::shapes::shape::Shapes"),
323 ];
324 let mut shape = TypeModel::new("Shape", TypeKind::Class, RustType::resolved("::base::shapes::shape::Shape"), "base::shapes::shape");
325 shape.namespace = "Base.Shapes".to_string();
326 let mut stroke = registered(Some("Stroke"), "stroke_property", RegistrationModel::Declared, None);
328 stroke.function_of = Some("::base::media::pen::Pen".to_string());
329 shape.registered = vec![registered(Some("Fill"), "fill_property", RegistrationModel::Declared, None), stroke];
330 base.types = vec![shape];
331 base.aliases = vec![
332 AliasModel { path: "::base::shapes::shape::Shapes".to_string(), target: RustType::resolved("Vec<::base::Shape>") },
333 AliasModel { path: "::base::shapes::shape::Figures".to_string(), target: RustType::resolved("Option<::base::shapes::shape::Shapes>") },
334 AliasModel { path: "::base::Lost".to_string(), target: RustType { text: "Vec<Missing>".to_string(), unresolved: vec!["Missing".to_string()] } },
335 ];
336
337 let mut other = AssemblyModel::new("Other", "other");
338 other.exports = vec![export("::other::rc::Rc", "::other::alloc::Rc")];
339
340 let mut controls = AssemblyModel::new("Controls", "controls");
341 let mut path = TypeModel::new("Path", TypeKind::Class, RustType::resolved("::controls::path::Path"), "controls::path");
342 path.registered = vec![
343 registered(None, "fill_property", RegistrationModel::AddedOwner, Some("::base::Shape::fill_property")),
344 registered(None, "paint_property", RegistrationModel::Alias, Some("::controls::path::Path::fill_property")),
345 registered(None, "lost_property", RegistrationModel::AddedOwner, Some("::missing::Type::lost_property")),
346 registered(None, "stroke_property", RegistrationModel::AddedOwner, Some("::base::Pen::stroke_property")),
347 ];
348 controls.types = vec![path];
349 ModelSet::new(vec![base, other, controls])
350 }
351
352 #[test]
355 fn paths_are_made_canonical_through_the_export_tables() {
356 let set = set();
357 assert_eq!(set.canonical_path("::base::Shape"), "::base::shapes::shape::Shape");
358 assert_eq!(set.canonical_path("::base::shapes::Shape::fill_property"), "::base::shapes::shape::Shape::fill_property");
359 assert_eq!(set.canonical_path("::base::Handle"), "::other::alloc::Rc");
360 assert_eq!(set.canonical_path("::base::media::Brush"), "::base::media::Brush");
361 assert_eq!(set.canonical_path("::base::Unknown"), "::base::Unknown");
362 assert_eq!(set.canonical_path("::std::rc::Rc"), "::std::rc::Rc");
363 assert_eq!(
364 set.canonical("Option<::base::Handle<dyn ::base::Shape>>"),
365 "Option<::other::alloc::Rc<dyn ::base::shapes::shape::Shape>>"
366 );
367 assert_eq!(set.canonical("&'static ::base::Shape"), "&'static ::base::shapes::shape::Shape");
368 assert_eq!(set.canonical("(f64, ::base::shapes::Shape)"), "(f64, ::base::shapes::shape::Shape)");
369 assert_eq!(set.canonical("Vec<f64>::base::Shape"), "Vec<f64>::base::Shape");
371 assert_eq!(set.canonical("String"), "String");
372 assert_eq!(ModelSet::new(Vec::new()).canonical("::base::Shape"), "::base::Shape");
373 }
374
375 #[test]
378 fn types_and_accessors_are_found_across_models() {
379 let set = set();
380 let found = set.find_rust_type("::base::Shape").map(|(model, type_)| (model.name.as_str(), type_.name.as_str()));
381 assert_eq!(found, Some(("Base", "Shape")));
382 assert_eq!(set.position_of_rust_type("::base::shapes::shape::Shape"), Some((0, 0)));
383 assert_eq!(set.position_of_rust_type("::controls::path::Path"), Some((2, 0)));
384 assert_eq!(set.position_of_rust_type("::base::Missing"), None);
385 assert_eq!(set.find_type("Base.Shapes.Shape").map(|(_, type_)| type_.name.as_str()), Some("Shape"));
386
387 let path = set.type_at((2, 0));
388 assert_eq!(set.name_of(&path.registered[0]), Some("Fill"));
389 assert_eq!(set.name_of(&path.registered[1]), Some("Fill"));
390 assert_eq!(set.name_of(&path.registered[2]), None);
391 assert_eq!(set.declaration_of(&path.registered[1]).map(|declared| declared.accessor.as_str()), Some("fill_property"));
392 assert_eq!(set.find_accessor("::base::Shape::fill_property").map(|(type_, _)| type_.name.as_str()), Some("Shape"));
393 assert_eq!(set.name_of(&path.registered[3]), Some("Stroke"));
396 let stroke = set.find_accessor("::base::Pen::stroke_property").map(|(type_, registered)| (type_.name.as_str(), registered.accessor.as_str()));
397 assert_eq!(stroke, Some(("Shape", "stroke_property")));
398 assert!(set.find_accessor("::base::Shape::stroke_property").is_none());
399 let shape = set.type_at((0, 0));
400 assert_eq!(set.accessor_type_path(shape, &shape.registered[0]), "::base::shapes::shape::Shape");
401 assert_eq!(set.accessor_type_path(shape, &shape.registered[1]), "::base::media::pen::Pen");
402
403 let mut names: Vec<(&str, &str)> = set.exported_names("::base").collect();
404 names.sort();
405 assert_eq!(names, vec![("Handle", "::other::rc::Rc"), ("Pen", "::base::media::pen::Pen"), ("Shape", "::base::shapes::shape::Shape"), ("Shapes", "::base::shapes::shape::Shapes")]);
406 }
407
408 #[test]
411 fn aliases_are_expanded_to_the_type_they_stand_for() {
412 let set = set();
413 const SHAPE: &str = "::base::shapes::shape::Shape";
414 assert_eq!(set.expanded("::base::Shapes"), format!("Vec<{SHAPE}>"));
415 assert_eq!(set.expanded("Option<::base::shapes::shape::Shapes>"), format!("Option<Vec<{SHAPE}>>"));
416 assert_eq!(set.expanded("::base::shapes::shape::Figures"), format!("Option<Vec<{SHAPE}>>"));
417 assert_eq!(set.expanded("::base::Shape"), SHAPE);
418 assert_eq!(set.expanded("::base::Lost"), "::base::Lost");
419 assert_eq!(set.expanded("f64"), "f64");
420 assert_eq!(set.canonical("::base::Shapes"), "::base::shapes::shape::Shapes");
421 }
422}