nostd_interactive_terminal/
parser.rs1use heapless::{String, Vec};
2
3#[derive(Debug, Clone)]
5pub struct ParsedCommand<const MAX_ARGS: usize, const BUF_SIZE: usize> {
6 pub command: String<BUF_SIZE>,
8 pub args: Vec<String<BUF_SIZE>, MAX_ARGS>,
10}
11
12impl<const MAX_ARGS: usize, const BUF_SIZE: usize> ParsedCommand<MAX_ARGS, BUF_SIZE> {
13 pub fn name(&self) -> &str {
15 &self.command
16 }
17
18 pub fn arg_count(&self) -> usize {
20 self.args.len()
21 }
22
23 pub fn arg(&self, index: usize) -> Option<&str> {
25 self.args.get(index).map(|s| s.as_str())
26 }
27
28 pub fn args_joined(&self, separator: &str) -> Option<String<BUF_SIZE>> {
30 if self.args.is_empty() {
31 return Some(String::new());
32 }
33
34 let mut result = String::new();
35 for (i, arg) in self.args.iter().enumerate() {
36 if i > 0 {
37 result.push_str(separator).ok()?;
38 }
39 result.push_str(arg).ok()?;
40 }
41 Some(result)
42 }
43
44 pub fn full_command(&self) -> Option<String<BUF_SIZE>> {
46 let mut result = self.command.clone();
47 for arg in &self.args {
48 result.push(' ').ok()?;
49 result.push_str(arg).ok()?;
50 }
51 Some(result)
52 }
53}
54
55pub struct CommandParser;
57
58impl CommandParser {
59 pub fn parse<const MAX_ARGS: usize, const BUF_SIZE: usize>(
63 input: &str,
64 ) -> Result<ParsedCommand<MAX_ARGS, BUF_SIZE>, ParseError> {
65 let trimmed = input.trim();
66 if trimmed.is_empty() {
67 return Err(ParseError::EmptyInput);
68 }
69
70 let mut parts = Vec::<String<BUF_SIZE>, MAX_ARGS>::new();
71 let mut current = String::<BUF_SIZE>::new();
72 let mut in_quotes = false;
73 let mut chars = trimmed.chars().peekable();
74
75 while let Some(c) = chars.next() {
76 match c {
77 '"' => {
78 in_quotes = !in_quotes;
79 }
80 ' ' if !in_quotes => {
81 if !current.is_empty() {
82 parts.push(current.clone()).map_err(|_| ParseError::TooManyArgs)?;
83 current.clear();
84 }
85 }
86 _ => {
87 current.push(c).map_err(|_| ParseError::ArgTooLong)?;
88 }
89 }
90 }
91
92 if !current.is_empty() {
94 parts.push(current).map_err(|_| ParseError::TooManyArgs)?;
95 }
96
97 if parts.is_empty() {
98 return Err(ParseError::EmptyInput);
99 }
100
101 let command = parts.remove(0);
102 let args = parts;
103
104 Ok(ParsedCommand { command, args })
105 }
106
107 pub fn parse_simple<const MAX_ARGS: usize, const BUF_SIZE: usize>(
109 input: &str,
110 ) -> Result<ParsedCommand<MAX_ARGS, BUF_SIZE>, ParseError> {
111 let trimmed = input.trim();
112 if trimmed.is_empty() {
113 return Err(ParseError::EmptyInput);
114 }
115
116 let mut parts = Vec::<String<BUF_SIZE>, MAX_ARGS>::new();
117
118 for part in trimmed.split_whitespace() {
119 let s = String::<BUF_SIZE>::try_from(part).map_err(|_| ParseError::ArgTooLong)?;
120 parts.push(s).map_err(|_| ParseError::TooManyArgs)?;
121 }
122
123 if parts.is_empty() {
124 return Err(ParseError::EmptyInput);
125 }
126
127 let command = parts.remove(0);
128 let args = parts;
129
130 Ok(ParsedCommand { command, args })
131 }
132
133 pub fn parse_max_split<const MAX_ARGS: usize, const BUF_SIZE: usize>(
135 input: &str,
136 max_splits: usize,
137 ) -> Result<ParsedCommand<MAX_ARGS, BUF_SIZE>, ParseError> {
138 let trimmed = input.trim();
139 if trimmed.is_empty() {
140 return Err(ParseError::EmptyInput);
141 }
142
143 let mut parts = Vec::<String<BUF_SIZE>, MAX_ARGS>::new();
144 let mut split_count = 0;
145 let mut remaining = trimmed;
146
147 while split_count < max_splits {
148 if let Some(pos) = remaining.find(' ') {
149 let (part, rest) = remaining.split_at(pos);
150 let s = String::<BUF_SIZE>::try_from(part.trim()).map_err(|_| ParseError::ArgTooLong)?;
151 parts.push(s).map_err(|_| ParseError::TooManyArgs)?;
152 remaining = rest.trim_start();
153 split_count += 1;
154 } else {
155 break;
156 }
157 }
158
159 if !remaining.is_empty() {
161 let s = String::<BUF_SIZE>::try_from(remaining).map_err(|_| ParseError::ArgTooLong)?;
162 parts.push(s).map_err(|_| ParseError::TooManyArgs)?;
163 }
164
165 if parts.is_empty() {
166 return Err(ParseError::EmptyInput);
167 }
168
169 let command = parts.remove(0);
170 let args = parts;
171
172 Ok(ParsedCommand { command, args })
173 }
174}
175
176#[derive(Debug, Clone, Copy, PartialEq)]
178pub enum ParseError {
179 EmptyInput,
180 TooManyArgs,
181 ArgTooLong,
182 UnclosedQuote,
183}
184
185#[cfg(test)]
186mod tests {
187 use super::*;
188
189 #[test]
190 fn test_parse_simple_command() {
191 let parsed: ParsedCommand<8, 64> = CommandParser::parse_simple("hello").unwrap();
192 assert_eq!(parsed.name(), "hello");
193 assert_eq!(parsed.arg_count(), 0);
194 }
195
196 #[test]
197 fn test_parse_with_args() {
198 let parsed: ParsedCommand<8, 64> =
199 CommandParser::parse_simple("send 192.168.1.1 message").unwrap();
200 assert_eq!(parsed.name(), "send");
201 assert_eq!(parsed.arg_count(), 2);
202 assert_eq!(parsed.arg(0), Some("192.168.1.1"));
203 assert_eq!(parsed.arg(1), Some("message"));
204 }
205
206 #[test]
207 fn test_parse_with_quotes() {
208 let parsed: ParsedCommand<8, 64> =
209 CommandParser::parse(r#"send peer "hello world""#).unwrap();
210 assert_eq!(parsed.name(), "send");
211 assert_eq!(parsed.arg_count(), 2);
212 assert_eq!(parsed.arg(1), Some("hello world"));
213 }
214
215 #[test]
216 fn test_parse_max_split() {
217 let parsed: ParsedCommand<8, 128> =
218 CommandParser::parse_max_split("broadcast this is a long message", 1).unwrap();
219 assert_eq!(parsed.name(), "broadcast");
220 assert_eq!(parsed.arg_count(), 1);
221 assert_eq!(parsed.arg(0), Some("this is a long message"));
222 }
223}