clipanion_core/
machine.rs

1use std::collections::{HashMap, HashSet};
2
3use crate::{actions::{Check, Reducer}, node::Node, shared::{is_terminal_node, Arg, CUSTOM_NODE_ID, ERROR_NODE_ID, INITIAL_NODE_ID, SUCCESS_NODE_ID}, transition::Transition};
4
5#[derive(Debug, Default)]
6pub struct MachineContext {
7    pub preferred_names: HashMap<String, String>,
8    pub valid_bindings: HashSet<String>,
9}
10
11pub struct Machine {
12    pub contexts: Vec<MachineContext>,
13    pub nodes: Vec<Node>,
14}
15
16impl std::fmt::Debug for Machine {
17    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
18        for (id, node) in self.nodes.iter().enumerate() {
19            writeln!(f, "Node {}:", id)?;
20
21            if id == ERROR_NODE_ID {
22                writeln!(f, "  [Error]")?;
23            } else if id == SUCCESS_NODE_ID {
24                writeln!(f, "  [Success]")?;
25            }
26
27            for (check, transition) in node.dynamics.iter() {
28                writeln!(f, "  Dynamic: {:?} -> {}", check, transition.to)?;
29            }
30
31            for transition in node.shortcuts.iter() {
32                writeln!(f, "  Shortcut -> {}", transition.to)?;
33            }
34
35            for (segment, transitions) in node.statics.iter() {
36                for transition in transitions.iter() {
37                    writeln!(f, "  Static: {:?} -> {}", segment, transition.to)?;
38                }
39            }
40        }
41
42        Ok(())
43    }
44}
45
46impl Default for Machine {
47    fn default() -> Machine {
48        let mut default = Machine {
49            contexts: vec![MachineContext::default()],
50            nodes: vec![],
51        };
52
53        for _ in 0..CUSTOM_NODE_ID {
54            default.nodes.push(Node::new());
55        }
56
57        default
58    }
59}
60
61impl Machine {
62    pub fn new() -> Machine {
63        Default::default()
64    }
65
66    pub fn new_any_of<I>(machines: I) -> Machine where I: IntoIterator<Item = Machine> {
67        let mut out = Machine::new();
68
69        for machine in machines {
70            let context_offset = out.contexts.len();
71            let node_offset = out.nodes.len();
72
73            out.contexts.extend(machine.contexts);
74            out.register_shortcut(INITIAL_NODE_ID, node_offset, Reducer::None);
75
76            for id in 0..machine.nodes.len() {
77                if !is_terminal_node(id) {
78                    let mut cloned_node = machine.nodes[id].clone_to_offset(node_offset);
79                    cloned_node.context += context_offset;
80                    out.nodes.push(cloned_node);
81                }
82            }
83        }
84
85        out
86    }
87
88    pub fn inject_node(&mut self, node: Node) -> usize {
89        self.nodes.push(node);
90        self.nodes.len() - 1
91    }
92
93    pub fn register_dynamic(&mut self, from: usize, check: Check, to: usize, reducer: Reducer) {
94        self.nodes[from].dynamics.push((check, Transition::new(to, reducer)));
95    }
96
97    pub fn register_shortcut(&mut self, from: usize, to: usize, reducer: Reducer) {
98        self.nodes[from].shortcuts.push(Transition::new(to, reducer));
99    }
100
101    pub fn register_static(&mut self, from: usize, key: Arg, to: usize, reducer: Reducer) {
102        self.nodes[from].statics.entry(key).or_default().push(Transition::new(to, reducer));
103    }
104
105    pub fn simplify_machine(&mut self) {
106        let mut visited = HashSet::new();
107        let mut queue = vec![INITIAL_NODE_ID];
108
109        while let Some(node) = queue.pop() {
110            if !visited.insert(node) {
111                continue;
112            }
113
114            let mut node_def = std::mem::take(&mut self.nodes[node]);
115
116            for (_, transition) in node_def.dynamics.iter() {
117                queue.push(transition.to);
118            }
119
120            for transition in node_def.shortcuts.iter() {
121                queue.push(transition.to);
122            }
123
124            for (_, transitions) in node_def.statics.iter() {
125                for transition in transitions.iter() {
126                    queue.push(transition.to);
127                }
128            }
129
130            let mut shortcuts: HashSet<usize>
131                = HashSet::from_iter(node_def.shortcuts.iter().map(|t| t.to));
132
133            while let Some(Transition {to, ..}) = node_def.shortcuts.pop() {
134                let to_def = self.nodes[to].clone();
135
136                for (segment, transitions) in to_def.statics.iter() {
137                    let store
138                        = node_def.statics.entry(segment.clone()).or_default();
139
140                    for transition in transitions {
141                        if !store.iter().any(|t| t.to == transition.to) {
142                            store.push(transition.clone());
143                        }
144                    }
145                }
146
147                for (check, transition) in to_def.dynamics.iter() {
148                    if !node_def.dynamics.iter().any(|(c, t)| c == check && t.to == transition.to) {
149                        node_def.dynamics.push((check.clone(), transition.clone()));
150                    }
151                }
152
153                for transition in to_def.shortcuts.iter() {
154                    if !shortcuts.contains(&transition.to) {
155                        node_def.shortcuts.push(transition.clone());
156                        shortcuts.insert(transition.to);
157                    }
158                }
159            }
160
161            self.nodes[node] = std::mem::take(&mut node_def);
162        }
163    }
164}