forester_rs/tree/
project.rs

1pub mod file;
2pub mod imports;
3
4use crate::read_file;
5use crate::runtime::action::ActionName;
6use crate::runtime::builder::{builtin, ros_core, ros_nav};
7use crate::tree::parser::ast::{FileEntity, Tree};
8use crate::tree::parser::Parser;
9use crate::tree::project::file::File;
10use crate::tree::{cerr, TreeError};
11use std::collections::{HashMap, HashSet};
12use std::path::PathBuf;
13
14pub type FileName = String;
15pub type TreeName = String;
16pub type AliasName = String;
17
18/// the base structure represents the folder on the disk with some auxiliary info
19/// ## Structure
20///   - `root` is a root of the project. Every import relates to it.
21///   - `main` is a pointer to the file and definition when the tree is started.
22///   - `files` is a map of the files
23///   - `std` is a set of the standard actions
24#[derive(Debug, Default, Clone)]
25pub struct Project {
26    pub root: PathBuf,
27    pub main: (FileName, TreeName),
28    pub files: HashMap<FileName, File>,
29    pub std: HashSet<ActionName>,
30}
31
32impl<'a> Project {
33    pub fn find_file(&'a self, f_name: &str) -> Result<&'a File, TreeError> {
34        self.files.get(f_name).ok_or(cerr(format!(
35            "unexpected error: the file {f_name} not exists"
36        )))
37    }
38    pub fn find_root(&'a self, name: &TreeName, file: &FileName) -> Result<&'a Tree, TreeError> {
39        self.find_file(file)?
40            .definitions
41            .get(name)
42            .ok_or(cerr(format!("no root {name} in {file}")))
43    }
44
45    pub fn find_tree(&self, file: &FileName, tree: &TreeName) -> Option<&Tree> {
46        self.files.get(file).and_then(|f| f.definitions.get(tree))
47    }
48
49    /// build the project with the given root and main file
50    ///
51    /// Suppose we have the following structure:
52    ///
53    /// - root_folder
54    ///     - folder
55    ///         - main.tree # root tree_name
56    ///     - other.tree
57    ///
58    /// Setting up the root as root_folder allows pulling in the other.tree file.
59    pub fn build_with_root(
60        main_file: FileName,
61        main_call: TreeName,
62        root: PathBuf,
63    ) -> Result<Project, TreeError> {
64        debug!(
65            target:"ast",
66            "built project with root: {:?}, main file: {} and root definition: {} ",
67            &root, main_file, main_call
68        );
69        let mut project = Project {
70            root: root.clone(),
71            main: ("".to_string(), "".to_string()),
72            files: Default::default(),
73            std: Default::default(),
74        };
75        project.main = (main_file.clone(), main_call);
76        project.parse_file(root, main_file)?;
77        Ok(project)
78    }
79    /// build the project with the given main file and root.
80    /// The root will be found in the main file.
81    /// If there are more than one root in the main file, the first one will be used.
82    pub fn build(main_file: FileName, root: PathBuf) -> Result<Project, TreeError> {
83        let mut project = Project {
84            root: root.clone(),
85            main: ("".to_string(), "".to_string()),
86            files: Default::default(),
87            std: Default::default(),
88        };
89
90        project.parse_file(root.clone(), main_file.clone())?;
91
92        let main_call = project
93            .files
94            .get(main_file.as_str())
95            .and_then(|file| file.definitions.iter().find(|(_name, t)| t.is_root()))
96            .map(|(name, _)| name.to_string())
97            .ok_or(TreeError::IOError(format!(
98                "no root operation in the file {}",
99                main_file.clone()
100            )))?;
101        debug!(
102           target:"ast",
103            "built project with root: {:?}, main file: {} and root definition: {} ",
104            &root, main_file, main_call
105        );
106        project.main = (main_file, main_call);
107        Ok(project)
108    }
109    /// build the project with the given text.
110    /// The root will be empty.
111    ///
112    /// # Note
113    /// If there are some imports to the other files they will not work
114    /// unless the imports are absolute,
115    pub fn build_from_text(text: String) -> Result<Project, TreeError> {
116        let mut project = Project {
117            root: PathBuf::new(),
118            main: ("".to_string(), "".to_string()),
119            files: Default::default(),
120            std: Default::default(),
121        };
122
123        project.parse_text(text)?;
124
125        let main_call = project
126            .files
127            .get("_")
128            .and_then(|file| file.definitions.iter().find(|(_name, t)| t.is_root()))
129            .map(|(name, _)| name.to_string())
130            .ok_or(TreeError::IOError(
131                "no root operation in the given text".to_string(),
132            ))?;
133        debug!(target:"ast","built project from text with root: {}", main_call);
134        project.main = ("_".to_string(), main_call);
135        Ok(project)
136    }
137
138    fn parse_text(&mut self, text: String) -> Result<(), TreeError> {
139        let ast_file = Parser::new(text.as_str())?.parse()?;
140
141        let mut file = File::new("_".to_string());
142        for ent in ast_file.0.into_iter() {
143            match ent {
144                FileEntity::Tree(t) => file.add_def(t)?,
145                FileEntity::Import(i) => {
146                    self.parse_file(PathBuf::new(), i.f_name().to_string())?;
147                    file.add_import(i)?
148                }
149            };
150        }
151
152        self.files.insert(file.name.clone(), file);
153        Ok(())
154    }
155
156    fn parse_file(&mut self, root: PathBuf, file: FileName) -> Result<(), TreeError> {
157        let text = file_to_str(root.clone(), file.clone())?;
158        let ast_file = Parser::new(text.as_str())?.parse()?;
159
160        if !self.files.contains_key(file.as_str()) {
161            let mut file = File::new(file);
162
163            for ent in ast_file.0.into_iter() {
164                match ent {
165                    FileEntity::Tree(t) => file.add_def(t)?,
166                    FileEntity::Import(i) => {
167                        self.parse_file(root.clone(), i.f_name().to_string())?;
168                        file.add_import(i)?
169                    }
170                };
171            }
172
173            self.files.insert(file.name.clone(), file);
174        }
175        Ok(())
176    }
177}
178fn file_to_str(root: PathBuf, file: FileName) -> Result<String, TreeError> {
179    if file.contains("::") {
180        let parts: Vec<_> = file.split("::").collect();
181        if parts.len() != 2 {
182            return Err(TreeError::IOError(format!("invalid file name: {}", file)));
183        } else {
184            match parts.as_slice() {
185                ["std", "actions"] => Ok(builtin::builtin_actions_file()),
186                ["ros", "nav2"] => Ok(ros_nav::ros_actions_file()),
187                ["ros", "core"] => Ok(ros_core::ros_actions_file()),
188                _ => Err(TreeError::IOError(format!("invalid file name: {}", file))),
189            }
190        }
191    } else {
192        let mut path = root;
193        path.push(file);
194        Ok(read_file(&path)?)
195    }
196}