Skip to main content

sys_rs/
command.rs

1use std::collections::BTreeMap;
2
3use crate::{
4    diag::{Error, Result},
5    handler::{self, CommandFn},
6    param::{Extend, Join, Type, Value},
7    progress::State,
8};
9
10#[derive(PartialEq, Debug)]
11enum Status {
12    Handled,
13    NotHandled,
14}
15
16enum Node {
17    Command(CommandFn, Vec<Type>),
18    Subcommands(Registry),
19    Alias(&'static str),
20    HelpCommand,
21}
22
23impl Node {
24    fn command_no_params(f: CommandFn) -> Self {
25        Node::Command(f, vec![])
26    }
27}
28
29/// Command registry describing the available REPL commands and their
30/// parameter signatures.
31///
32/// The `Registry` stores command nodes (concrete commands, subcommand groups
33/// and aliases) in a deterministic `BTreeMap` so help/usage output is stable.
34pub struct Registry {
35    // Using a BTreeMap to have a deterministic order for display purposes
36    nodes: BTreeMap<&'static str, Vec<Node>>,
37}
38
39impl Registry {
40    fn new() -> Self {
41        Self {
42            nodes: BTreeMap::new(),
43        }
44    }
45
46    fn register(mut self, name: &'static str, node: Node) -> Self {
47        self.nodes.entry(name).or_default().push(node);
48        self
49    }
50
51    fn alias(mut self, alias: &'static str, target: &'static str) -> Self {
52        self.nodes
53            .entry(alias)
54            .or_default()
55            .push(Node::Alias(target));
56        self
57    }
58
59    fn walk<'a, F>(&'a self, prefix: &mut Vec<&'a str>, f: &mut F)
60    where
61        F: FnMut(&[&'a str], Option<&'a [Type]>),
62    {
63        for (name, nodes) in &self.nodes {
64            if nodes.iter().all(|n| matches!(n, Node::Alias(_))) {
65                continue;
66            }
67
68            if !name.is_empty() {
69                prefix.push(name);
70            }
71
72            for entry in nodes {
73                match entry {
74                    Node::Command(_, param_types) => f(prefix, Some(param_types)),
75                    Node::Subcommands(sub) => sub.walk(prefix, f),
76                    Node::Alias(_) => {}
77                    Node::HelpCommand => f(&[*name], None),
78                }
79            }
80
81            if !name.is_empty() {
82                prefix.pop();
83            }
84        }
85    }
86
87    fn collect<F>(&self, mut f: F)
88    where
89        F: FnMut(&[&str], Option<&[Type]>),
90    {
91        let mut prefix = Vec::new();
92        self.walk(&mut prefix, &mut f);
93    }
94
95    #[must_use]
96    /// Return a list of concrete command spellings suitable for completion.
97    ///
98    /// Each entry is a space-separated command spelling (including parent
99    /// subcommands). The results are deduplicated and are intended to be used
100    /// by the REPL completion helper.
101    ///
102    /// # Returns
103    ///
104    /// A deduplicated `Vec<String>` containing space-separated command
105    /// spellings (including parent subcommands) suitable for use by the
106    /// REPL completion helper.
107    pub fn completions(&self) -> Vec<String> {
108        let mut out = Vec::new();
109
110        self.collect(|names, _| {
111            out.push(names.join(" "));
112        });
113
114        out.dedup();
115        out
116    }
117
118    #[must_use]
119    /// Return human-friendly usage lines for every command.
120    ///
121    /// Each usage entry contains the full command spelling (including parent
122    /// subcommands) and the parameter signature (e.g. `breakpoint <address>`).
123    /// This is suitable for printing when the user requests help.
124    ///
125    /// # Returns
126    ///
127    /// A `Vec<String>` where each entry contains a command spelling and its
128    /// parameter signature (for example `breakpoint <address>`), intended
129    /// for help output.
130    pub fn usages(&self) -> Vec<String> {
131        let mut out = Vec::new();
132
133        self.collect(|names, param_types| {
134            let mut usage = names.join(" ");
135            if let Some(params) = param_types {
136                if !params.is_empty() {
137                    let params_str = params
138                        .iter()
139                        .map(std::string::ToString::to_string)
140                        .collect::<Vec<_>>()
141                        .join(" ");
142                    usage.push(' ');
143                    usage.push_str(&params_str);
144                }
145            }
146
147            out.push(usage);
148        });
149
150        out
151    }
152
153    fn parse_params<'a>(
154        param_types: &[Type],
155        params: &'a [&'a str],
156    ) -> Vec<Value<'a>> {
157        param_types
158            .iter()
159            .enumerate()
160            .filter_map(|(i, param_type)| Value::new(param_type, params[i]).ok())
161            .collect()
162    }
163
164    fn handle_command(
165        handler: CommandFn,
166        param_types: &[Type],
167        rest: &[&str],
168        state: &mut State,
169    ) -> Result<Status> {
170        let mut status = Status::NotHandled;
171
172        let num_params = param_types.len();
173        if rest.len() == num_params {
174            let parsed = Self::parse_params(param_types, rest);
175            if parsed.len() == num_params {
176                handler(&parsed, state)?;
177                status = Status::Handled;
178            }
179        }
180
181        Ok(status)
182    }
183
184    fn handle_subcommands(
185        registry: &Registry,
186        path: &[Value],
187        first: &str,
188        rest: &[&str],
189        state: &mut State,
190    ) -> Result<()> {
191        let path = path.extend(first, &[]);
192        if rest.is_empty() {
193            handler::do_ambiguous(&path, state)
194        } else {
195            registry.dispatch(rest, &path, state)
196        }
197    }
198
199    fn handle_help(
200        &self,
201        path: &[Value],
202        first: &str,
203        rest: &[&str],
204        state: &mut State,
205    ) -> Result<()> {
206        if rest.is_empty() {
207            let commands = self.usages();
208            let commands: Vec<Value> =
209                commands.iter().map(|s| Value::String(s)).collect();
210            handler::do_help(&commands, state)
211        } else {
212            let path = path.extend(first, rest);
213            handler::do_unknown(&path, state)
214        }
215    }
216
217    fn handle_no_entry(
218        &self,
219        path: &[Value],
220        first: &str,
221        rest: &[&str],
222        state: &mut State,
223    ) -> Result<()> {
224        let matches: Vec<_> = self
225            .nodes
226            .keys()
227            .filter(|name| name.starts_with(first))
228            .collect();
229
230        match matches.len() {
231            1 => {
232                let mut args = vec![*matches[0]];
233                args.extend_from_slice(rest);
234                self.dispatch(&args, path, state)
235            }
236            n if n > 1 => {
237                let path = path.extend(first, &[]);
238                handler::do_ambiguous(&path, state)
239            }
240            _ => {
241                let path = path.extend(first, rest);
242                handler::do_unknown(&path, state)
243            }
244        }
245    }
246
247    fn handle_node(
248        &self,
249        node: &Node,
250        path: &[Value],
251        first: &str,
252        rest: &[&str],
253        state: &mut State,
254    ) -> Result<bool> {
255        match node {
256            Node::Command(handler, param_types) => {
257                match Self::handle_command(*handler, param_types, rest, state)? {
258                    Status::Handled => Ok(true),
259                    Status::NotHandled => Ok(false),
260                }
261            }
262            Node::Subcommands(registry) => {
263                let res =
264                    Self::handle_subcommands(registry, path, first, rest, state);
265                Ok(res.is_ok())
266            }
267            Node::Alias(target) => {
268                let mut new_args = vec![*target];
269                new_args.extend_from_slice(rest);
270                let res = self.dispatch(&new_args, path, state);
271                Ok(res.is_ok())
272            }
273            Node::HelpCommand => {
274                let res = self.handle_help(path, first, rest, state);
275                Ok(res.is_ok())
276            }
277        }
278    }
279
280    fn dispatch(
281        &self,
282        args: &[&str],
283        path: &[Value],
284        state: &mut State,
285    ) -> Result<()> {
286        match args.split_first() {
287            Some((first, rest)) => match self.nodes.get(*first) {
288                Some(nodes) => {
289                    let handled =
290                        nodes.iter().try_fold(false, |handled, node| {
291                            Ok::<bool, Error>(
292                                handled
293                                    | self.handle_node(
294                                        node, path, first, rest, state,
295                                    )?,
296                            )
297                        })?;
298                    if !handled {
299                        let path = path.extend(first, &[]);
300                        let args = format!("{}: {}", path.join(" "), rest.join(" "));
301                        handler::do_invalid_arguments(
302                            &[Value::String(&args)],
303                            state,
304                        )?;
305                    }
306                    Ok(())
307                }
308                None => self.handle_no_entry(path, first, rest, state),
309            },
310            None => handler::do_nothing(&[], state),
311        }
312    }
313
314    /// Parse `input` and dispatch the corresponding command handler.
315    ///
316    /// # Arguments
317    ///
318    /// * `input` - The raw user input line (e.g. `breakpoint 0x400123`).
319    /// * `state` - Mutable reference to runtime `State` used by handlers.
320    ///
321    /// # Errors
322    ///
323    /// Returns an error if dispatching a command fails; handlers return
324    /// `Result` which is propagated to the caller.
325    pub fn run(&self, input: &str, state: &mut State) -> Result<()> {
326        let args: Vec<&str> = input.split_whitespace().collect();
327        self.dispatch(&args, &[], state)?;
328        Ok(())
329    }
330}
331
332impl Default for Registry {
333    fn default() -> Self {
334        let info_registry = Registry::new()
335            .register(
336                "breakpoints",
337                Node::command_no_params(handler::do_info_breakpoints),
338            )
339            .register("memory", Node::command_no_params(handler::do_info_memory))
340            .register(
341                "registers",
342                Node::command_no_params(handler::do_info_registers),
343            );
344
345        let layout_registry = Registry::new()
346            .register("asm", Node::command_no_params(handler::do_layout_asm))
347            .register("src", Node::command_no_params(handler::do_layout_src));
348
349        Registry::new()
350            .register("backtrace", Node::command_no_params(handler::do_backtrace))
351            .alias("bt", "backtrace")
352            .register(
353                "breakpoint",
354                Node::command_no_params(handler::do_breakpoint),
355            )
356            .register(
357                "breakpoint",
358                Node::Command(handler::do_breakpoint, vec![Type::Address]),
359            )
360            .alias("b", "breakpoint") // Remove ambiguity with backtrace
361            .register("continue", Node::command_no_params(handler::do_continue))
362            .register("delete", Node::Command(handler::do_delete, vec![Type::Id]))
363            .register(
364                "examine",
365                Node::Command(
366                    handler::do_examine,
367                    vec![Type::Format, Type::Size, Type::Address],
368                ),
369            )
370            .alias("x", "examine")
371            .register("help", Node::HelpCommand)
372            .register("info", Node::Subcommands(info_registry))
373            .register("layout", Node::Subcommands(layout_registry))
374            .register("list", Node::command_no_params(handler::do_list))
375            .alias("l", "list") // Remove ambiguity with layout
376            .register("quit", Node::command_no_params(handler::do_quit))
377            .register("step", Node::command_no_params(handler::do_step))
378            .register("next", Node::command_no_params(handler::do_next))
379            .register(
380                "tbreakpoint",
381                Node::command_no_params(handler::do_tbreakpoint),
382            )
383            .register(
384                "tbreakpoint",
385                Node::Command(handler::do_tbreakpoint, vec![Type::Address]),
386            )
387    }
388}
389
390#[cfg(test)]
391mod tests {
392    use super::*;
393
394    use nix::unistd::Pid;
395
396    use crate::{
397        param::{Type, Value},
398        progress::{Execution, State},
399    };
400
401    #[test]
402    fn test_registry_completions_and_usages() {
403        let reg = Registry::new()
404            .register("foo", Node::command_no_params(handler::do_nothing))
405            .register(
406                "bar",
407                Node::Command(handler::do_nothing, vec![Type::Address, Type::Id]),
408            );
409
410        let comps = reg.completions();
411        assert!(comps.contains(&"foo".to_string()));
412        assert!(comps.contains(&"bar".to_string()));
413
414        let usages = reg.usages();
415        assert!(usages.iter().any(|s| s.starts_with("bar ")));
416    }
417
418    #[test]
419    fn test_handle_command_param_matching() {
420        fn my_handler(_args: &[Value], _state: &mut State) -> Result<()> {
421            Ok(())
422        }
423
424        let mut state = State::new(Pid::from_raw(1), None);
425        let res = Registry::handle_command(
426            my_handler,
427            &[Type::Address],
428            &["0x100"],
429            &mut state,
430        )
431        .expect("handle_command failed");
432        assert_eq!(res, Status::Handled);
433
434        let res = Registry::handle_command(
435            my_handler,
436            &[Type::Address, Type::Id],
437            &["0x100"],
438            &mut state,
439        )
440        .expect("handle_command failed");
441        assert_eq!(res, Status::NotHandled);
442    }
443
444    #[test]
445    fn test_handle_no_entry_ambiguous() {
446        let reg = Registry::new()
447            .register("foo", Node::command_no_params(handler::do_nothing))
448            .register("fop", Node::command_no_params(handler::do_nothing));
449
450        let mut state = State::new(Pid::from_raw(1), None);
451        reg.dispatch(&["fo"], &[], &mut state)
452            .expect("dispatch failed");
453        assert!(matches!(state.execution(), Execution::Skip));
454    }
455
456    #[test]
457    fn test_alias_dispatch() {
458        let reg = Registry::new()
459            .register("target", Node::command_no_params(handler::do_nothing))
460            .alias("a", "target");
461
462        let mut state = State::new(Pid::from_raw(1), None);
463        reg.dispatch(&["a"], &[], &mut state)
464            .expect("dispatch failed");
465        assert!(matches!(state.execution(), Execution::Skip));
466    }
467
468    #[test]
469    fn test_handle_help_and_run() {
470        let reg = Registry::new().register("help", Node::HelpCommand);
471
472        let mut state = State::new(Pid::from_raw(1), None);
473        reg.dispatch(&["help"], &[], &mut state)
474            .expect("dispatch failed");
475        assert!(matches!(state.execution(), Execution::Skip));
476
477        let mut state2 = State::new(Pid::from_raw(1), None);
478        reg.run("help", &mut state2).expect("run failed");
479        assert!(matches!(state2.execution(), Execution::Skip));
480    }
481}