forester_rs/tree/project/
imports.rs

1use crate::tree::parser::ast::{ImportName, Tree};
2use crate::tree::project::{AliasName, File, FileName, Project, TreeName};
3use crate::tree::{cerr, TreeError};
4use std::collections::{HashMap, HashSet};
5
6/// reordering by tree definition
7#[derive(Default)]
8pub struct ImportMap {
9    pub aliases: HashMap<AliasName, TreeName>,
10    pub trees: HashMap<TreeName, FileName>,
11    pub files: HashSet<FileName>,
12}
13
14impl ImportMap {
15    /// processes the imports checking there is no crossing between aliases and definitions
16    /// ## Note
17    /// For now, when the import of the whole file there is no validations on crossings and other things.
18    /// Thus, better off to perform imports only for the used definitions.
19    pub fn build(file: &File) -> Result<Self, TreeError> {
20        let mut map = ImportMap::default();
21        for (file, items) in &file.imports {
22            for item in items {
23                match item {
24                    ImportName::Id(v) => {
25                        if map.trees.get(v).filter(|f| f != &file).is_some() {
26                            return Err(cerr(format!("the import call {v} is presented twice from several different files")));
27                        }
28                        if map.aliases.get(v).is_some() {
29                            return Err(cerr(format!("the import call {v} is presented as alias")));
30                        }
31                        map.trees.insert(v.to_string(), file.to_string());
32                    }
33                    ImportName::Alias(id, alias) => {
34                        if map.aliases.get(alias).filter(|idt| *idt != id).is_some() {
35                            return Err(cerr(format!(
36                                "the import alias {alias} is already defined for another call "
37                            )));
38                        }
39                        map.aliases.insert(alias.to_string(), id.to_string());
40                        map.trees.insert(id.to_string(), file.to_string());
41                    }
42                    ImportName::WholeFile => {
43                        map.files.insert(file.to_string());
44                    }
45                }
46            }
47        }
48
49        Ok(map)
50    }
51
52    /// find the tree in the project considering the aliases and the definitions
53    pub fn find<'a>(
54        &'a self,
55        key: &TreeName,
56        project: &'a Project,
57    ) -> Result<(&'a Tree, &'a FileName), TreeError> {
58        // try to find by name
59        if let Some(file) = self.trees.get(key) {
60            project
61                .find_tree(file, key)
62                .map(|t| (t, file))
63                .ok_or(cerr(format!(
64                    "the call {key} can not be found in the file {file} "
65                )))
66            // try to find by alias
67        } else if let Some(id) = self.aliases.get(key) {
68            let file = self
69                .trees
70                .get(id)
71                .ok_or(cerr(format!("the call {id} is not presented")))?;
72            project
73                .find_tree(file, id)
74                .map(|t| (t, file))
75                .ok_or(cerr(format!(
76                    "the call {key} can not be found in the file {file} "
77                )))
78        } else {
79            // try to find bluntly everywhere. Probably this is not the best idea
80            self.files
81                .iter()
82                .flat_map(|f| project.files.get(f))
83                .find(|f| f.definitions.contains_key(key))
84                .and_then(|f| f.definitions.get(key).map(|t| (t, &f.name)))
85                .ok_or(cerr(format!(
86                    "the call {key} can not be found among the file in the project"
87                )))
88        }
89    }
90}