forester_rs/runtime/
rtree.rs

1pub mod analyzer;
2pub mod builder;
3pub mod iter;
4pub mod macros;
5pub mod rnode;
6pub mod transform;
7
8use crate::runtime::action::ActionName;
9use crate::runtime::args::transform::{to_dec_rt_args, to_rt_args};
10
11use crate::runtime::rtree::rnode::{DecoratorType, RNode, RNodeId};
12use crate::runtime::rtree::transform::{StackItem, Transformer};
13use crate::runtime::{RtOk, RtResult, RuntimeError};
14use crate::tree::parser::ast::call::Call;
15
16use crate::runtime::rtree::analyzer::RtTreeAnalyzer;
17use crate::runtime::rtree::iter::RtTreeBfsIter;
18use crate::tree::project::imports::ImportMap;
19use crate::tree::project::{FileName, Project};
20use crate::tree::{cerr, TreeError};
21use std::collections::{HashMap, HashSet, VecDeque};
22use std::path::PathBuf;
23use crate::converter::Converter;
24use crate::converter::to_nav::ToRosNavConverter;
25use log::debug;
26
27/// The auxiliary structure that encapsulates the runtime tree
28/// and some additional information about actions
29pub struct RuntimeTreeStarter {
30    pub tree: RuntimeTree,
31    // the separate tables for standard and all actions
32    // the reason for this is that the user actions can have the same name as the standard ones
33    // and we need to distinguish them
34    pub std_actions: HashSet<(ActionName, FileName)>,
35    pub actions: HashSet<ActionName>,
36}
37
38/// The runtime tree is a representation of the compilation tree supplemented with some runtime information.
39#[derive(Default, Debug, PartialEq)]
40pub struct RuntimeTree {
41    pub root: RNodeId,
42    pub nodes: HashMap<RNodeId, RNode>,
43}
44
45impl RuntimeTree {
46    /// Returns bfs iterator over the runtime tree
47    pub fn iter(&self) -> RtTreeBfsIter<'_> {
48        RtTreeBfsIter {
49            queue: VecDeque::from(vec![self.root]),
50            tree: &self,
51        }
52    }
53    /// Returns the analyzer for the runtime tree
54    /// which provides methods to analyze the tree
55    /// and find nodes by some criteria
56    ///
57    /// # Example
58    ///
59    /// ```
60    ///     use forester_rs::runtime::args::RtArgs;
61    ///     use forester_rs::runtime::rtree::builder::RtNodeBuilder;
62    ///     use forester_rs::runtime::rtree::builder::RtTreeBuilder;
63    ///     use forester_rs::runtime::rtree::rnode::FlowType;
64    ///     use forester_rs::runtime::rtree::rnode::RNodeName;
65    ///     use forester_rs::*;
66    ///
67    ///     #[test]
68    ///     fn analyzer() {
69    ///         let mut rtb = RtTreeBuilder::new();
70    ///
71    ///         let flow = flow!(fallback node_name!("root"), args!();
72    ///             flow!(sequence node_name!("seq"), args!();
73    ///                  action!(node_name!("action1"))
74    ///             ),
75    ///            action!(node_name!("action2"))
76    ///         );
77    ///
78    ///         rtb.add_as_root(flow);
79    ///         let tree = rtb.build().unwrap().0;
80    ///
81    ///         let analyzer = tree.analyze();
82    ///
83    ///         let a1 = analyzer.find_id_by(|n| n.is_name("action1")).unwrap();
84    ///         let a2 = analyzer.find_id_by(|n| n.is_name("action2")).unwrap();
85    ///         let root = analyzer.find_id_by(|n| n.is_name("root")).unwrap();
86    ///         let seq = analyzer.find_id_by(|n| n.is_name("seq")).unwrap();
87    ///
88    ///         assert_eq!(analyzer.parent(&a1), Some(&seq));
89    ///         assert_eq!(analyzer.parent(&a2), Some(&root));
90    ///         assert_eq!(analyzer.parent(&root), None);
91    ///     }
92    /// ```
93    pub fn analyze(&self) -> RtTreeAnalyzer<'_> {
94        RtTreeAnalyzer::new(self)
95    }
96    /// Builds the runtime tree from the project
97    pub fn build(project: Project) -> Result<RuntimeTreeStarter, TreeError> {
98        let (file, name) = &project.main;
99        let root = project.find_root(name, file)?;
100        let mut builder = Transformer::default();
101        let mut r_tree = RuntimeTree::default();
102        let mut std_actions = HashSet::new();
103        let mut actions = HashSet::new();
104
105        let root_id = builder.next();
106        builder.add_chain_root(root_id);
107
108        let children = builder.push_vec(root.calls.clone(), root_id, file.clone());
109        let root_node = RNode::root(root.name.to_string(), file.clone(), children);
110        r_tree.root = root_id;
111        r_tree.nodes.insert(root_id, root_node);
112
113        while let Some(item) = builder.pop() {
114            let StackItem {
115                id,
116                call,
117                parent_id,
118                file_name,
119            } = item;
120
121            let curr_file = &project.find_file(file_name.as_str())?;
122            let import_map = ImportMap::build(curr_file)?;
123            match call {
124                // for lambda there is not many actions since it does not have arguments so just grab a type and children
125                Call::Lambda(tpe, calls) => {
126                    debug!(target:"tree[construct]", "found lambda {tpe}: id {id} and parent {parent_id}");
127                    let children = builder.push_vec(calls, id, file_name.clone());
128                    builder.add_chain_lambda(id, parent_id);
129                    r_tree
130                        .nodes
131                        .insert(id, RNode::lambda(tpe.try_into()?, children));
132                }
133                // for higher order invocation there are two possible cases:
134                // - the invocation is passed as an argument from the parent (this chain can be long up)
135                //   So we need to find the initially passed call.
136                // - since we found it we transform it into a simple invocation call and process it at the next step.
137                // - if it is lambda we already found it
138                Call::HoInvocation(key) => {
139                    debug!(target:"tree[construct]", "found ho invocation with id {id} in parent {parent_id}");
140                    let (p_id, _parent_args, _parent_params) =
141                    builder.get_chain_skip_lambda(&parent_id)?.get_tree();
142                    let call = builder.find_ho_call(&parent_id, &key)?;
143                    if call.is_lambda() || call.is_decorator() {
144                        builder.push_front(id, call, p_id, file_name.clone());
145                    } else {
146                        let k = call
147                            .key()
148                            .ok_or(cerr(format!("the call {:?} does not have a name. Therefore, it is no possible to invoke it by name.", call)))?;
149
150                        builder.push_front(
151                            id,
152                            Call::invocation(&k, call.arguments()),
153                            p_id,
154                            file_name.clone(),
155                        );
156                    }
157                }
158                // just take the arguments and transform them into runtime args and push further
159                Call::Decorator(tpe, decor_args, call) => {
160                    debug!(target:"tree[construct]", "found decorator {tpe}, id {id} in parent {parent_id}");
161                    let (_, parent_args, parent_params) =
162                        builder.get_chain_skip_lambda(&parent_id)?.get_tree();
163                    builder.add_chain(id, parent_id, parent_args.clone(), parent_params.clone());
164                    let child = builder.push(*call, id, file.clone());
165                    let d_tpe: DecoratorType = tpe.try_into()?;
166                    let rt_args = to_dec_rt_args(&d_tpe, decor_args, parent_args, parent_params)?;
167                    r_tree
168                        .nodes
169                        .insert(id, RNode::decorator(d_tpe, rt_args, child));
170                }
171                // firstly we need to find the definition either in the file or in the imports
172                // with a consideration of a possible alias and transform the args
173                Call::Invocation(name, args) => {
174                    debug!(target:"tree[construct]", "found invocation , id {id} in parent {parent_id}");
175                    let (_, parent_args, parent_params) = builder
176                        .get_chain_skip_lambda(&parent_id)
177                        .map(|e| e.get_tree())
178                        .unwrap_or_default();
179                    match curr_file.definitions.get(&name) {
180                        Some(tree) => {
181                            let (rt_args, upd_args) = to_rt_args(
182                                name.as_str(),
183                                args.clone(),
184                                tree.params.clone(),
185                                parent_args,
186                                parent_params,
187                            )?;
188                            builder.add_chain(id, parent_id, upd_args, tree.params.clone());
189                            if tree.tpe.is_action() {
190                                r_tree.nodes.insert(id, RNode::action(name, curr_file.name.clone(), rt_args));
191                                actions.insert(tree.name.clone());
192                            } else {
193                                let children =
194                                    builder.push_vec(tree.calls.clone(), id, file_name.clone());
195                                r_tree.nodes.insert(
196                                    id,
197                                    RNode::flow(tree.tpe.try_into()?, name, curr_file.name.clone(), rt_args, children),
198                                );
199                            }
200                        }
201                        None => {
202                            debug!(target:"tree[construct]", "found import from another file,  id {id} in parent {parent_id}");
203                            let (tree, file) = import_map.find(&name, &project)?;
204                            if file.contains("::") {
205                                std_actions.insert((tree.name.clone(), file.clone()));
206                            }
207                            let (rt_args, upd_args) = to_rt_args(
208                                name.as_str(),
209                                args.clone(),
210                                tree.params.clone(),
211                                parent_args,
212                                parent_params,
213                            )?;
214                            builder.add_chain(id, parent_id, upd_args, tree.params.clone());
215                            let children =
216                                builder.push_vec(tree.calls.clone(), id, file_name.clone());
217
218                            if tree.name != name {
219                                if tree.tpe.is_action() {
220                                    actions.insert(tree.name.clone());
221                                    r_tree.nodes.insert(
222                                        id,
223                                        RNode::action_alias(tree.name.clone(), file.clone(), name, rt_args),
224                                    );
225                                } else {
226                                    r_tree.nodes.insert(
227                                        id,
228                                        RNode::flow_alias(
229                                            tree.tpe.try_into()?,
230                                            tree.name.clone(),
231                                            file.clone(),
232                                            name,
233                                            rt_args,
234                                            children,
235                                        ),
236                                    );
237                                }
238                            } else if tree.tpe.is_action() {
239                                r_tree
240                                    .nodes
241                                    .insert(id, RNode::action(name.clone(), file.clone(), rt_args));
242                                actions.insert(name);
243                            } else {
244                                r_tree.nodes.insert(
245                                    id,
246                                    RNode::flow(tree.tpe.try_into()?, name, file.clone(), rt_args, children),
247                                );
248                            };
249                        }
250                    }
251                }
252            }
253        }
254
255        Ok(RuntimeTreeStarter {
256            tree: r_tree,
257            std_actions,
258            actions,
259        })
260    }
261    /// Returns the node by id
262    pub fn node(&self, id: &RNodeId) -> RtResult<&RNode> {
263        self.nodes.get(id).ok_or(RuntimeError::uex(format!(
264            "the node {id} is not found in the rt tree"
265        )))
266    }
267
268    /// find the max given id in the tree
269    pub fn max_id(&self) -> RNodeId {
270        self.nodes.keys().max().cloned().unwrap_or_default()
271    }
272
273    /// Converts the runtime tree into the ROS navigation xml file
274    pub fn to_ros_nav(&self, xml: PathBuf) -> RtOk {
275        ToRosNavConverter::new(&self, xml).convert()
276    }
277}
278
279#[cfg(test)]
280mod tests {
281    use crate::runtime::args::{RtArgs, RtArgument, RtValue};
282    use crate::runtime::rtree::rnode::FlowType::{Fallback, Root, Sequence};
283    use crate::runtime::rtree::rnode::RNode::{Flow, Leaf};
284    use crate::runtime::rtree::rnode::RNodeName::{Lambda, Name};
285    use crate::runtime::rtree::RuntimeTree;
286    use crate::tree::project::Project;
287    use std::collections::{HashSet};
288    use itertools::Itertools;
289    use crate::tests::turn_on_logs;
290
291
292    #[test]
293    fn smoke() {
294        let project = Project::build_from_text(
295            r#"
296          import "std::actions"
297          impl action();
298          root main fallback{
299                sequence {
300                    action()
301                    success()
302                }
303            }        
304        "#
305                .to_string(),
306        )
307            .unwrap();
308
309        let st_tree = RuntimeTree::build(project).unwrap();
310
311        assert_eq!(
312            st_tree.std_actions,
313            HashSet::from_iter(vec![("success".to_string(), "std::actions".to_string())])
314        );
315        assert_eq!(
316            st_tree.actions,
317            HashSet::from_iter(vec!["action".to_string(), "success".to_string()])
318        );
319        assert_eq!(st_tree.tree.nodes.len(), 5);
320        assert_eq!(st_tree.tree.root, 1);
321        assert_eq!(st_tree.tree.max_id(), 5);
322        let items: Vec<_> = st_tree.tree.nodes.iter().sorted_by_key(|e| e.0).collect();
323        assert_eq!(
324            items,
325            vec![
326                (
327                    &1usize,
328                    &Flow(Root, Name("main".to_string(), "_".to_string()), RtArgs(vec![]), vec![2])
329                ),
330                (&2usize, &Flow(Fallback, Lambda, RtArgs(vec![]), vec![3])),
331                (&3usize, &Flow(Sequence, Lambda, RtArgs(vec![]), vec![4, 5])),
332                (&4usize, &Leaf(Name("action".to_string(), "_".to_string()), RtArgs(vec![]))),
333                (&5usize, &Leaf(Name("success".to_string(), "std::actions".to_string()), RtArgs(vec![]))),
334            ]
335        );
336    }
337
338    #[test]
339    fn decorator_lambda() {
340        let project = Project::build_from_text(
341            r#"
342          impl action();
343          root main f(t = retry(1) action())
344          sequence f(t:tree) t(..)
345        "#
346                .to_string(),
347        )
348            .unwrap();
349
350        let st_tree = RuntimeTree::build(project).unwrap().tree;
351
352        assert_eq!(st_tree.nodes.len(), 4)
353    }
354
355    #[test]
356    fn params() {
357        let project = Project::build_from_text(
358            r#"
359        impl consumer(arg:any);
360
361        root main test(1)
362
363        sequence test(a:any){
364            test2(a)
365        }
366
367        sequence test2(a1:num){
368            consumer(a1)
369        }
370        "#
371                .to_string(),
372        )
373            .unwrap();
374
375        let st_tree = RuntimeTree::build(project).unwrap().tree;
376
377        let items: Vec<_> = st_tree.nodes.iter().sorted_by_key(|e| e.0).collect();
378        assert_eq!(
379            items,
380            vec![
381                (
382                    &1usize,
383                    &Flow(
384                        Root,
385                        Name("main".to_string(), "_".to_string()),
386                        RtArgs(vec![]),
387                        vec![2],
388                    )
389                ),
390                (
391                    &2usize,
392                    &Flow(
393                        Sequence,
394                        Name("test".to_string(), "_".to_string()),
395                        RtArgs(vec![RtArgument::new(
396                            "a".to_string(),
397                            RtValue::int(1))]),
398                        vec![3],
399                    )
400                ),
401                (
402                    &3usize,
403                    &Flow(
404                        Sequence,
405                        Name("test2".to_string(), "_".to_string()),
406                        RtArgs(vec![RtArgument::new(
407                            "a1".to_string(),
408                            RtValue::int(1))]),
409                        vec![4],
410                    )
411                ),
412                (
413                    &4usize,
414                    &Leaf(
415                        Name("consumer".to_string(), "_".to_string()),
416                        RtArgs(vec![RtArgument::new(
417                            "arg".to_string(),
418                            RtValue::int(1))]),
419                    )
420                ),
421            ]
422        );
423    }
424
425    #[test]
426    fn params2() {
427        let project = Project::build_from_text(
428            r#"
429        impl consumer(arg0:any,arg:any);
430        root main test("-b-",1)
431
432        sequence test(b:any,a:any){
433            test2(a,b)
434        }
435
436        sequence test2(b1:num,a1:any){
437            consumer(b1,a1)
438        }
439        "#
440                .to_string(),
441        )
442            .unwrap();
443
444        let st_tree = RuntimeTree::build(project).unwrap().tree;
445
446        let item = st_tree.nodes.iter().find(|(id, _)| **id == 4).unwrap().1.args();
447        assert_eq!(
448            item,
449            RtArgs(vec![
450                RtArgument::new("arg0".to_string(), RtValue::int(1)),
451                RtArgument::new("arg".to_string(), RtValue::str("-b-".to_string())),
452            ])
453        );
454    }
455
456}