Skip to main content

ruvix_shell/
parser.rs

1//! Command line parser for the RuVix debug shell.
2//!
3//! This module implements a simple line-based parser that handles
4//! command parsing and argument extraction for the debug shell.
5
6use alloc::string::{String, ToString};
7use alloc::vec::Vec;
8use core::fmt;
9
10/// Parsed command representation.
11#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Command {
13    /// Show help information.
14    Help,
15    /// Show kernel information.
16    Info,
17    /// Show memory statistics.
18    Mem,
19    /// Show task list.
20    Tasks,
21    /// Show capability table.
22    Caps {
23        /// Optional task ID to filter by.
24        task_id: Option<u32>,
25    },
26    /// Show queue statistics.
27    Queues,
28    /// Show vector store info.
29    Vectors,
30    /// Show proof statistics.
31    Proofs,
32    /// Show CPU information.
33    Cpu,
34    /// Show witness log entries.
35    Witness {
36        /// Number of entries to show (default: 10).
37        count: usize,
38    },
39    /// Show performance counters.
40    Perf,
41    /// Toggle or show syscall tracing.
42    Trace {
43        /// None = show status, Some(true) = enable, Some(false) = disable.
44        enable: Option<bool>,
45    },
46    /// Trigger system reboot.
47    Reboot,
48}
49
50/// Parse error types.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum ParseError {
53    /// Unknown command.
54    UnknownCommand(String),
55    /// Invalid argument.
56    InvalidArgument {
57        /// The command that received the invalid argument.
58        command: String,
59        /// The invalid argument value.
60        argument: String,
61        /// Expected format description.
62        expected: String,
63    },
64    /// Empty input.
65    EmptyInput,
66}
67
68impl fmt::Display for ParseError {
69    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
70        match self {
71            Self::UnknownCommand(cmd) => write!(f, "Unknown command: '{}'", cmd),
72            Self::InvalidArgument {
73                command,
74                argument,
75                expected,
76            } => write!(
77                f,
78                "Invalid argument '{}' for command '{}': expected {}",
79                argument, command, expected
80            ),
81            Self::EmptyInput => write!(f, "Empty input"),
82        }
83    }
84}
85
86/// Command line parser.
87#[derive(Debug, Default)]
88pub struct Parser {
89    _private: (),
90}
91
92impl Parser {
93    /// Create a new parser.
94    #[must_use]
95    pub const fn new() -> Self {
96        Self { _private: () }
97    }
98
99    /// Parse a command line into a Command.
100    ///
101    /// # Errors
102    ///
103    /// Returns `ParseError` if the command is unknown or arguments are invalid.
104    pub fn parse(&self, line: &str) -> Result<Command, ParseError> {
105        let trimmed = line.trim();
106        if trimmed.is_empty() {
107            return Err(ParseError::EmptyInput);
108        }
109
110        let tokens: Vec<&str> = trimmed.split_whitespace().collect();
111        let cmd = tokens[0].to_lowercase();
112        let args = &tokens[1..];
113
114        match cmd.as_str() {
115            "help" | "?" | "h" => Ok(Command::Help),
116            "info" | "version" => Ok(Command::Info),
117            "mem" | "memory" => Ok(Command::Mem),
118            "tasks" | "ps" | "processes" => Ok(Command::Tasks),
119            "caps" | "capabilities" => self.parse_caps(args),
120            "queues" | "queue" | "q" => Ok(Command::Queues),
121            "vectors" | "vec" | "v" => Ok(Command::Vectors),
122            "proofs" | "proof" | "p" => Ok(Command::Proofs),
123            "cpu" | "cpus" | "smp" => Ok(Command::Cpu),
124            "witness" | "wit" | "w" => self.parse_witness(args),
125            "perf" | "performance" | "counters" => Ok(Command::Perf),
126            "trace" | "strace" => self.parse_trace(args),
127            "reboot" | "restart" | "reset" => Ok(Command::Reboot),
128            _ => Err(ParseError::UnknownCommand(cmd)),
129        }
130    }
131
132    /// Parse the `caps` command arguments.
133    fn parse_caps(&self, args: &[&str]) -> Result<Command, ParseError> {
134        let task_id = if args.is_empty() {
135            None
136        } else {
137            match args[0].parse::<u32>() {
138                Ok(id) => Some(id),
139                Err(_) => {
140                    return Err(ParseError::InvalidArgument {
141                        command: "caps".to_string(),
142                        argument: args[0].to_string(),
143                        expected: "task ID (u32)".to_string(),
144                    })
145                }
146            }
147        };
148        Ok(Command::Caps { task_id })
149    }
150
151    /// Parse the `witness` command arguments.
152    fn parse_witness(&self, args: &[&str]) -> Result<Command, ParseError> {
153        let count = if args.is_empty() {
154            10 // Default to 10 entries
155        } else {
156            match args[0].parse::<usize>() {
157                Ok(n) => n,
158                Err(_) => {
159                    return Err(ParseError::InvalidArgument {
160                        command: "witness".to_string(),
161                        argument: args[0].to_string(),
162                        expected: "entry count (usize)".to_string(),
163                    })
164                }
165            }
166        };
167        Ok(Command::Witness { count })
168    }
169
170    /// Parse the `trace` command arguments.
171    fn parse_trace(&self, args: &[&str]) -> Result<Command, ParseError> {
172        let enable = if args.is_empty() {
173            None
174        } else {
175            match args[0].to_lowercase().as_str() {
176                "on" | "enable" | "1" | "true" | "yes" => Some(true),
177                "off" | "disable" | "0" | "false" | "no" => Some(false),
178                _ => {
179                    return Err(ParseError::InvalidArgument {
180                        command: "trace".to_string(),
181                        argument: args[0].to_string(),
182                        expected: "on/off, enable/disable, 1/0, true/false, or yes/no".to_string(),
183                    })
184                }
185            }
186        };
187        Ok(Command::Trace { enable })
188    }
189
190    /// Get all available command names (for completion).
191    #[must_use]
192    pub fn command_names(&self) -> &'static [&'static str] {
193        &[
194            "help", "info", "mem", "tasks", "caps", "queues", "vectors", "proofs", "cpu",
195            "witness", "perf", "trace", "reboot",
196        ]
197    }
198}
199
200#[cfg(test)]
201mod tests {
202    use super::*;
203
204    #[test]
205    fn test_parse_help() {
206        let parser = Parser::new();
207        assert_eq!(parser.parse("help"), Ok(Command::Help));
208        assert_eq!(parser.parse("?"), Ok(Command::Help));
209        assert_eq!(parser.parse("h"), Ok(Command::Help));
210        assert_eq!(parser.parse("HELP"), Ok(Command::Help));
211    }
212
213    #[test]
214    fn test_parse_info() {
215        let parser = Parser::new();
216        assert_eq!(parser.parse("info"), Ok(Command::Info));
217        assert_eq!(parser.parse("version"), Ok(Command::Info));
218    }
219
220    #[test]
221    fn test_parse_mem() {
222        let parser = Parser::new();
223        assert_eq!(parser.parse("mem"), Ok(Command::Mem));
224        assert_eq!(parser.parse("memory"), Ok(Command::Mem));
225    }
226
227    #[test]
228    fn test_parse_tasks() {
229        let parser = Parser::new();
230        assert_eq!(parser.parse("tasks"), Ok(Command::Tasks));
231        assert_eq!(parser.parse("ps"), Ok(Command::Tasks));
232        assert_eq!(parser.parse("processes"), Ok(Command::Tasks));
233    }
234
235    #[test]
236    fn test_parse_caps() {
237        let parser = Parser::new();
238        assert_eq!(parser.parse("caps"), Ok(Command::Caps { task_id: None }));
239        assert_eq!(
240            parser.parse("caps 42"),
241            Ok(Command::Caps { task_id: Some(42) })
242        );
243        assert!(matches!(
244            parser.parse("caps invalid"),
245            Err(ParseError::InvalidArgument { .. })
246        ));
247    }
248
249    #[test]
250    fn test_parse_queues() {
251        let parser = Parser::new();
252        assert_eq!(parser.parse("queues"), Ok(Command::Queues));
253        assert_eq!(parser.parse("queue"), Ok(Command::Queues));
254        assert_eq!(parser.parse("q"), Ok(Command::Queues));
255    }
256
257    #[test]
258    fn test_parse_vectors() {
259        let parser = Parser::new();
260        assert_eq!(parser.parse("vectors"), Ok(Command::Vectors));
261        assert_eq!(parser.parse("vec"), Ok(Command::Vectors));
262        assert_eq!(parser.parse("v"), Ok(Command::Vectors));
263    }
264
265    #[test]
266    fn test_parse_proofs() {
267        let parser = Parser::new();
268        assert_eq!(parser.parse("proofs"), Ok(Command::Proofs));
269        assert_eq!(parser.parse("proof"), Ok(Command::Proofs));
270        assert_eq!(parser.parse("p"), Ok(Command::Proofs));
271    }
272
273    #[test]
274    fn test_parse_cpu() {
275        let parser = Parser::new();
276        assert_eq!(parser.parse("cpu"), Ok(Command::Cpu));
277        assert_eq!(parser.parse("cpus"), Ok(Command::Cpu));
278        assert_eq!(parser.parse("smp"), Ok(Command::Cpu));
279    }
280
281    #[test]
282    fn test_parse_witness() {
283        let parser = Parser::new();
284        assert_eq!(parser.parse("witness"), Ok(Command::Witness { count: 10 }));
285        assert_eq!(parser.parse("witness 5"), Ok(Command::Witness { count: 5 }));
286        assert_eq!(parser.parse("wit 20"), Ok(Command::Witness { count: 20 }));
287        assert!(matches!(
288            parser.parse("witness invalid"),
289            Err(ParseError::InvalidArgument { .. })
290        ));
291    }
292
293    #[test]
294    fn test_parse_perf() {
295        let parser = Parser::new();
296        assert_eq!(parser.parse("perf"), Ok(Command::Perf));
297        assert_eq!(parser.parse("performance"), Ok(Command::Perf));
298        assert_eq!(parser.parse("counters"), Ok(Command::Perf));
299    }
300
301    #[test]
302    fn test_parse_trace() {
303        let parser = Parser::new();
304        assert_eq!(parser.parse("trace"), Ok(Command::Trace { enable: None }));
305        assert_eq!(
306            parser.parse("trace on"),
307            Ok(Command::Trace { enable: Some(true) })
308        );
309        assert_eq!(
310            parser.parse("trace off"),
311            Ok(Command::Trace { enable: Some(false) })
312        );
313        assert_eq!(
314            parser.parse("trace enable"),
315            Ok(Command::Trace { enable: Some(true) })
316        );
317        assert_eq!(
318            parser.parse("trace disable"),
319            Ok(Command::Trace { enable: Some(false) })
320        );
321        assert_eq!(
322            parser.parse("trace 1"),
323            Ok(Command::Trace { enable: Some(true) })
324        );
325        assert_eq!(
326            parser.parse("trace 0"),
327            Ok(Command::Trace { enable: Some(false) })
328        );
329        assert!(matches!(
330            parser.parse("trace invalid"),
331            Err(ParseError::InvalidArgument { .. })
332        ));
333    }
334
335    #[test]
336    fn test_parse_reboot() {
337        let parser = Parser::new();
338        assert_eq!(parser.parse("reboot"), Ok(Command::Reboot));
339        assert_eq!(parser.parse("restart"), Ok(Command::Reboot));
340        assert_eq!(parser.parse("reset"), Ok(Command::Reboot));
341    }
342
343    #[test]
344    fn test_parse_unknown() {
345        let parser = Parser::new();
346        assert!(matches!(
347            parser.parse("unknown"),
348            Err(ParseError::UnknownCommand(_))
349        ));
350    }
351
352    #[test]
353    fn test_parse_empty() {
354        let parser = Parser::new();
355        assert_eq!(parser.parse(""), Err(ParseError::EmptyInput));
356        assert_eq!(parser.parse("   "), Err(ParseError::EmptyInput));
357    }
358
359    #[test]
360    fn test_parse_whitespace_handling() {
361        let parser = Parser::new();
362        assert_eq!(parser.parse("  help  "), Ok(Command::Help));
363        assert_eq!(
364            parser.parse("  caps   42  "),
365            Ok(Command::Caps { task_id: Some(42) })
366        );
367    }
368
369    #[test]
370    fn test_command_names() {
371        let parser = Parser::new();
372        let names = parser.command_names();
373        assert!(names.contains(&"help"));
374        assert!(names.contains(&"info"));
375        assert!(names.contains(&"reboot"));
376        assert_eq!(names.len(), 13);
377    }
378}