clipanion_core/
machine.rs1use 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}