1use alloc::string::{String, ToString};
7use alloc::vec::Vec;
8use core::fmt;
9
10#[derive(Debug, Clone, PartialEq, Eq)]
12pub enum Command {
13 Help,
15 Info,
17 Mem,
19 Tasks,
21 Caps {
23 task_id: Option<u32>,
25 },
26 Queues,
28 Vectors,
30 Proofs,
32 Cpu,
34 Witness {
36 count: usize,
38 },
39 Perf,
41 Trace {
43 enable: Option<bool>,
45 },
46 Reboot,
48}
49
50#[derive(Debug, Clone, PartialEq, Eq)]
52pub enum ParseError {
53 UnknownCommand(String),
55 InvalidArgument {
57 command: String,
59 argument: String,
61 expected: String,
63 },
64 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#[derive(Debug, Default)]
88pub struct Parser {
89 _private: (),
90}
91
92impl Parser {
93 #[must_use]
95 pub const fn new() -> Self {
96 Self { _private: () }
97 }
98
99 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 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 fn parse_witness(&self, args: &[&str]) -> Result<Command, ParseError> {
153 let count = if args.is_empty() {
154 10 } 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 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 #[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}