forc_debug/cli/
commands.rs

1use crate::{cli::state::DebuggerHelper, error::Result};
2use std::collections::HashSet;
3use strsim::levenshtein;
4
5#[derive(Debug, Clone)]
6pub struct Command {
7    pub name: &'static str,
8    pub aliases: &'static [&'static str],
9    pub help: &'static str,
10}
11
12pub struct Commands {
13    pub tx: Command,
14    pub reset: Command,
15    pub continue_: Command,
16    pub step: Command,
17    pub breakpoint: Command,
18    pub registers: Command,
19    pub memory: Command,
20    pub quit: Command,
21    pub help: Command,
22}
23
24impl Commands {
25    pub const fn new() -> Self {
26        Self {
27            tx: Command {
28                name: "start_tx",
29                aliases: &["n", "tx", "new_tx"],
30                help: "Start a new transaction",
31            },
32            reset: Command {
33                name: "reset",
34                aliases: &[],
35                help: "Reset debugger state",
36            },
37            continue_: Command {
38                name: "continue",
39                aliases: &["c"],
40                help: "Continue execution",
41            },
42            step: Command {
43                name: "step",
44                aliases: &["s"],
45                help: "Step execution",
46            },
47            breakpoint: Command {
48                name: "breakpoint",
49                aliases: &["b"],
50                help: "Set breakpoint",
51            },
52            registers: Command {
53                name: "register",
54                aliases: &["r", "reg", "registers"],
55                help: "View registers",
56            },
57            memory: Command {
58                name: "memory",
59                aliases: &["m", "mem"],
60                help: "View memory",
61            },
62            quit: Command {
63                name: "quit",
64                aliases: &["exit"],
65                help: "Exit debugger",
66            },
67            help: Command {
68                name: "help",
69                aliases: &["h", "?"],
70                help: "Show help for commands",
71            },
72        }
73    }
74
75    pub fn all_commands(&self) -> Vec<&Command> {
76        vec![
77            &self.tx,
78            &self.reset,
79            &self.continue_,
80            &self.step,
81            &self.breakpoint,
82            &self.registers,
83            &self.memory,
84            &self.quit,
85            &self.help,
86        ]
87    }
88
89    pub fn is_tx_command(&self, cmd: &str) -> bool {
90        self.tx.name == cmd || self.tx.aliases.contains(&cmd)
91    }
92
93    pub fn is_register_command(&self, cmd: &str) -> bool {
94        self.registers.name == cmd || self.registers.aliases.contains(&cmd)
95    }
96
97    pub fn is_quit_command(&self, cmd: &str) -> bool {
98        self.quit.name == cmd || self.quit.aliases.contains(&cmd)
99    }
100
101    pub fn is_help_command(&self, cmd: &str) -> bool {
102        self.help.name == cmd || self.help.aliases.contains(&cmd)
103    }
104
105    pub fn find_command(&self, name: &str) -> Option<&Command> {
106        self.all_commands()
107            .into_iter()
108            .find(|cmd| cmd.name == name || cmd.aliases.contains(&name))
109    }
110
111    /// Returns a set of all valid command strings including aliases
112    pub fn get_all_command_strings(&self) -> HashSet<&'static str> {
113        let mut commands = HashSet::new();
114        for cmd in self.all_commands() {
115            commands.insert(cmd.name);
116            commands.extend(cmd.aliases);
117        }
118        commands
119    }
120
121    /// Suggests a similar command
122    pub fn find_closest(&self, unknown_cmd: &str) -> Option<&Command> {
123        self.all_commands()
124            .into_iter()
125            .flat_map(|cmd| {
126                std::iter::once((cmd, cmd.name))
127                    .chain(cmd.aliases.iter().map(move |&alias| (cmd, alias)))
128            })
129            .map(|(cmd, name)| (cmd, levenshtein(unknown_cmd, name)))
130            .filter(|&(_, distance)| distance <= 2)
131            .min_by_key(|&(_, distance)| distance)
132            .map(|(cmd, _)| cmd)
133    }
134}
135
136// Add help command implementation:
137pub async fn cmd_help(helper: &DebuggerHelper, args: &[String]) -> Result<()> {
138    if args.len() > 1 {
139        // Help for specific command
140        if let Some(cmd) = helper.commands.find_command(&args[1]) {
141            println!("{} - {}", cmd.name, cmd.help);
142            if !cmd.aliases.is_empty() {
143                println!("Aliases: {}", cmd.aliases.join(", "));
144            }
145            return Ok(());
146        }
147        println!("Unknown command: '{}'", args[1]);
148    }
149
150    println!("Available commands:");
151    for cmd in helper.commands.all_commands() {
152        println!("  {:<12} - {}", cmd.name, cmd.help);
153        if !cmd.aliases.is_empty() {
154            println!("    aliases: {}", cmd.aliases.join(", "));
155        }
156    }
157    Ok(())
158}
159
160/// Parses a string representing a number and returns it as a `usize`.
161///
162/// The input string can be in decimal or hexadecimal format:
163/// - Decimal numbers are parsed normally (e.g., `"123"`).
164/// - Hexadecimal numbers must be prefixed with `"0x"` (e.g., `"0x7B"`).
165/// - Underscores can be used as visual separators (e.g., `"1_000"` or `"0x1_F4"`).
166///
167/// If the input string is not a valid number in the specified format, `None` is returned.
168///
169/// # Examples
170///
171/// ```
172/// use forc_debug::cli::parse_int;
173/// /// Use underscores as separators in decimal and hexadecimal numbers
174/// assert_eq!(parse_int("123"), Some(123));
175/// assert_eq!(parse_int("1_000"), Some(1000));
176///
177/// /// Parse hexadecimal numbers with "0x" prefix
178/// assert_eq!(parse_int("0x7B"), Some(123));
179/// assert_eq!(parse_int("0x1_F4"), Some(500));
180///
181/// /// Handle invalid inputs gracefully
182/// assert_eq!(parse_int("abc"), None);
183/// assert_eq!(parse_int("0xZZZ"), None);
184/// assert_eq!(parse_int(""), None);
185/// ```
186///
187/// # Errors
188///
189/// Returns `None` if the input string contains invalid characters,
190/// is not properly formatted, or cannot be parsed into a `usize`.
191pub fn parse_int(s: &str) -> Option<usize> {
192    let (s, radix) = s.strip_prefix("0x").map_or((s, 10), |s| (s, 16));
193    usize::from_str_radix(&s.replace('_', ""), radix).ok()
194}