1use std::fmt;
20
21#[derive(Debug, Clone)]
27pub enum Param {
28 Numeric(f64),
29 Bool(bool),
30 String(String),
31}
32
33#[derive(Debug)]
35pub struct Command {
36 pub index: usize,
38 pub params: Vec<Param>,
40 pub suffixes: Vec<u32>,
43}
44
45pub type Handler = fn(&Command);
47
48pub struct CommandSet {
50 entries: Vec<Entry>,
51}
52
53#[derive(Debug, Clone, Copy, PartialEq)]
58enum ParamKind {
59 Num,
60 Bool,
61 Str,
62}
63
64#[derive(Debug, Clone)]
66enum Segment {
67 Keyword { short: String, long: String },
69 NumericSuffix,
71}
72
73struct Entry {
75 segments: Vec<Segment>,
77 optional_groups: Vec<OptGroup>,
79 is_query: bool,
80 param: Option<ParamKind>,
81 handler: Handler,
82}
83
84#[derive(Debug, Clone)]
87struct OptGroup {
88 start: usize,
89 end: usize, }
91
92#[derive(Debug)]
93pub struct ParseError(String);
94
95impl fmt::Display for ParseError {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 f.write_str(&self.0)
98 }
99}
100
101impl std::error::Error for ParseError {}
102
103fn short_long(token: &str) -> (String, String) {
110 let long = token.to_ascii_uppercase();
111 let short: String = token
113 .chars()
114 .take_while(|c| c.is_ascii_uppercase() || c.is_ascii_digit() || *c == '+' || *c == '-')
115 .collect::<String>()
116 .to_ascii_uppercase();
117 (short, long)
119}
120
121fn parse_keyword_segments(chars: &[char], pos: &mut usize) -> Vec<Segment> {
128 if chars[*pos] == '#' {
129 *pos += 1;
130 return vec![Segment::NumericSuffix];
131 }
132
133 let start = *pos;
134 while *pos < chars.len() && !matches!(chars[*pos], ':' | '[' | ']' | '#') {
135 *pos += 1;
136 }
137 let token: String = chars[start..*pos].iter().collect();
138 let (short, long) = short_long(&token);
139 let kw = Segment::Keyword {
140 short: short.to_ascii_uppercase(),
141 long: long.to_ascii_uppercase(),
142 };
143
144 if *pos < chars.len() && chars[*pos] == '#' {
145 *pos += 1;
146 vec![kw, Segment::NumericSuffix]
147 } else {
148 vec![kw]
149 }
150}
151
152fn compile(
154 pattern: &str,
155) -> Result<(Vec<Segment>, Vec<OptGroup>, bool, Option<ParamKind>), ParseError> {
156 let (kw_part, param) = if pattern.ends_with(" num") {
157 (&pattern[..pattern.len() - 4], Some(ParamKind::Num))
158 } else if pattern.ends_with(" bool") {
159 (&pattern[..pattern.len() - 5], Some(ParamKind::Bool))
160 } else if pattern.ends_with(" str") {
161 (&pattern[..pattern.len() - 4], Some(ParamKind::Str))
162 } else {
163 (pattern, None)
164 };
165
166 let is_query = kw_part.ends_with('?');
167 let kw_part = if is_query {
168 &kw_part[..kw_part.len() - 1]
169 } else {
170 kw_part
171 };
172
173 let mut segments: Vec<Segment> = Vec::new();
174 let mut opt_groups: Vec<OptGroup> = Vec::new();
175
176 let chars: Vec<char> = kw_part.chars().collect();
177 let mut i = 0;
178
179 while i < chars.len() {
180 if chars[i] == ':' {
181 i += 1;
182 continue;
183 }
184
185 if chars[i] == '[' {
186 let group_start = segments.len();
187 i += 1;
188 if i < chars.len() && chars[i] == ':' {
189 i += 1;
190 }
191 while i < chars.len() && chars[i] != ']' {
192 if chars[i] == ':' {
193 i += 1;
194 continue;
195 }
196 let segs = parse_keyword_segments(&chars, &mut i);
197 segments.extend(segs);
198 }
199 if i < chars.len() && chars[i] == ']' {
200 i += 1;
201 }
202 opt_groups.push(OptGroup {
203 start: group_start,
204 end: segments.len(),
205 });
206 } else {
207 let segs = parse_keyword_segments(&chars, &mut i);
208 segments.extend(segs);
209 }
210 }
211
212 Ok((segments, opt_groups, is_query, param))
213}
214
215fn keyword_matches(short: &str, long: &str, input: &str) -> bool {
224 let ilen = input.len();
225 if ilen < short.len() || ilen > long.len() {
226 return false;
227 }
228 long[..ilen] == *input
230}
231
232fn tokenise_command(input: &str) -> (Vec<String>, Option<String>) {
243 let input = input.trim();
244 if input.is_empty() {
245 return (vec![], None);
246 }
247
248 let bytes = input.as_bytes();
252 let mut kw_end = input.len();
253 for (j, &b) in bytes.iter().enumerate() {
255 if b == b'\'' || b == b'"' {
256 kw_end = j;
258 break;
259 }
260 if b == b' ' || b == b'\t' {
261 kw_end = j;
262 break;
263 }
264 }
265
266 let kw_str = &input[..kw_end];
267 let param_str = input[kw_end..].trim();
268 let param = if param_str.is_empty() {
269 None
270 } else {
271 Some(param_str.to_string())
272 };
273
274 let tokens: Vec<String> = if kw_str.starts_with('*') {
277 vec![kw_str.to_ascii_uppercase()]
279 } else {
280 kw_str
281 .split(':')
282 .filter(|s| !s.is_empty())
283 .map(|s| s.to_ascii_uppercase())
284 .collect()
285 };
286
287 (tokens, param)
288}
289
290fn extract_suffix(token: &str, short: &str, long: &str) -> Option<(String, u32)> {
294 for kw in &[long, short] {
298 if token.len() >= kw.len() && token[..kw.len()] == **kw {
299 let rest = &token[kw.len()..];
300 if rest.is_empty() {
301 return Some((kw.to_string(), 1));
303 }
304 if let Ok(n) = rest.parse::<u32>() {
305 return Some((kw.to_string(), n));
306 }
307 }
308 }
309 None
310}
311
312fn try_match(
319 entry: &Entry,
320 tokens: &[String],
321 input_is_query: bool,
322) -> Option<Vec<u32>> {
323 if input_is_query != entry.is_query {
324 return None;
325 }
326
327 let n_opt = entry.optional_groups.len();
331 let combos = 1u32 << n_opt;
332
333 for combo in 0..combos {
334 let mut active_segments: Vec<&Segment> = Vec::new();
336 for (idx, seg) in entry.segments.iter().enumerate() {
337 let mut skipped = false;
339 for (g, grp) in entry.optional_groups.iter().enumerate() {
340 if idx >= grp.start && idx < grp.end && (combo >> g) & 1 == 0 {
341 skipped = true;
342 break;
343 }
344 }
345 if !skipped {
346 active_segments.push(seg);
347 }
348 }
349
350 if let Some(suffixes) = match_segments(&active_segments, tokens) {
351 return Some(suffixes);
352 }
353 }
354
355 None
356}
357
358fn match_segments(segments: &[&Segment], tokens: &[String]) -> Option<Vec<u32>> {
361 let mut suffixes = Vec::new();
362 let mut ti = 0; let mut si = 0; while si < segments.len() {
366 match &segments[si] {
367 Segment::Keyword { short, long } => {
368 if ti >= tokens.len() {
369 return None;
370 }
371 let token = &tokens[ti];
372
373 let next_is_suffix =
376 si + 1 < segments.len() && matches!(segments[si + 1], Segment::NumericSuffix);
377
378 if next_is_suffix {
379 let (_, suf) = extract_suffix(token, short, long)?;
380 suffixes.push(suf);
381 si += 2; ti += 1;
383 } else {
384 if !keyword_matches(short, long, token) {
385 return None;
386 }
387 si += 1;
388 ti += 1;
389 }
390 }
391 Segment::NumericSuffix => {
392 if ti >= tokens.len() {
395 return None;
396 }
397 if let Ok(n) = tokens[ti].parse::<u32>() {
398 suffixes.push(n);
399 ti += 1;
400 si += 1;
401 } else {
402 return None;
403 }
404 }
405 }
406 }
407
408 if ti == tokens.len() {
410 Some(suffixes)
411 } else {
412 None
413 }
414}
415
416fn strip_unit_suffix(raw: &str) -> &str {
423 let bytes = raw.as_bytes();
425 let mut end = bytes.len();
426 while end > 0 && bytes[end - 1].is_ascii_alphabetic() {
427 end -= 1;
428 }
429 if end == bytes.len() {
430 return raw; }
432 if end == 0 {
433 return raw; }
435 let suffix = &raw[end..];
438 if (suffix.starts_with('e') || suffix.starts_with('E'))
439 && bytes[end - 1].is_ascii_digit()
440 && suffix[1..].chars().all(|c| c.is_ascii_digit() || c == '+' || c == '-')
441 {
442 return raw;
444 }
445 raw[..end].trim_end()
446}
447
448fn parse_param(kind: ParamKind, raw: &str) -> Result<Param, ParseError> {
449 let raw = raw.trim();
450 match kind {
451 ParamKind::Num => {
452 let num_str = strip_unit_suffix(raw);
457 if num_str.starts_with("#H") || num_str.starts_with("#h") {
458 let val =
459 u64::from_str_radix(&num_str[2..], 16).map_err(|e| ParseError(e.to_string()))?;
460 Ok(Param::Numeric(val as f64))
461 } else if num_str.starts_with("#Q") || num_str.starts_with("#q") {
462 let val =
463 u64::from_str_radix(&num_str[2..], 8).map_err(|e| ParseError(e.to_string()))?;
464 Ok(Param::Numeric(val as f64))
465 } else if num_str.starts_with("#B") || num_str.starts_with("#b") {
466 let val =
467 u64::from_str_radix(&num_str[2..], 2).map_err(|e| ParseError(e.to_string()))?;
468 Ok(Param::Numeric(val as f64))
469 } else {
470 let val: f64 = num_str
471 .parse()
472 .map_err(|e: std::num::ParseFloatError| ParseError(e.to_string()))?;
473 Ok(Param::Numeric(val))
474 }
475 }
476 ParamKind::Bool => {
477 let upper = raw.to_ascii_uppercase();
478 match upper.as_str() {
479 "ON" | "1" => Ok(Param::Bool(true)),
480 "OFF" | "0" => Ok(Param::Bool(false)),
481 _ => Err(ParseError(format!("invalid boolean: {raw}"))),
482 }
483 }
484 ParamKind::Str => {
485 if (raw.starts_with('"') && raw.ends_with('"'))
487 || (raw.starts_with('\'') && raw.ends_with('\''))
488 {
489 Ok(Param::String(raw[1..raw.len() - 1].to_string()))
490 } else {
491 Ok(Param::String(raw.to_string()))
492 }
493 }
494 }
495}
496
497impl CommandSet {
502 pub fn from_table(table: &[(&str, Handler)]) -> Result<Self, ParseError> {
504 let mut entries = Vec::with_capacity(table.len());
505 for (pattern, handler) in table {
506 let (segments, optional_groups, is_query, param) = compile(pattern)?;
507 entries.push(Entry {
508 segments,
509 optional_groups,
510 is_query,
511 param,
512 handler: *handler,
513 });
514 }
515 Ok(CommandSet { entries })
516 }
517
518 pub fn parse(&self, line: &str) -> Result<Vec<Command>, ParseError> {
521 let line = line.trim();
522 if line.is_empty() {
523 return Ok(vec![]);
524 }
525
526 let raw_cmds = split_commands(line);
528 let mut result = Vec::new();
529
530 for raw in &raw_cmds {
531 let raw = raw.trim();
532 if raw.is_empty() {
533 continue;
534 }
535 let cmd = self.parse_single(raw)?;
536 result.push(cmd);
537 }
538
539 Ok(result)
540 }
541
542 fn parse_single(&self, input: &str) -> Result<Command, ParseError> {
543 let (tokens, param_str) = tokenise_command(input);
544 if tokens.is_empty() {
545 return Err(ParseError("empty command".into()));
546 }
547
548 let mut tokens = tokens;
550 let mut is_query = false;
551 if let Some(last) = tokens.last_mut() {
552 if last.ends_with('?') {
553 is_query = true;
554 last.truncate(last.len() - 1);
555 if last.is_empty() {
556 tokens.pop();
558 }
559 }
560 }
561
562 for (idx, entry) in self.entries.iter().enumerate() {
564 if let Some(suffixes) = try_match(entry, &tokens, is_query) {
565 let params = if let Some(kind) = entry.param {
567 let raw = param_str.as_deref().unwrap_or("");
568 if raw.is_empty() {
569 return Err(ParseError(format!(
570 "command expects a parameter but none given"
571 )));
572 }
573 vec![parse_param(kind, raw)?]
574 } else {
575 vec![]
576 };
577 return Ok(Command {
578 index: idx,
579 params,
580 suffixes,
581 });
582 }
583 }
584
585 Err(ParseError(format!("unrecognised command: {input}")))
586 }
587
588 pub fn dispatch(&self, cmd: &Command) {
590 (self.entries[cmd.index].handler)(cmd);
591 }
592}
593
594fn split_commands(line: &str) -> Vec<String> {
600 let mut parts = Vec::new();
601 let mut current = String::new();
602 let mut in_quotes = false;
603 let mut quote_char = ' ';
604
605 for ch in line.chars() {
606 if in_quotes {
607 current.push(ch);
608 if ch == quote_char {
609 in_quotes = false;
610 }
611 } else if ch == '\'' || ch == '"' {
612 in_quotes = true;
613 quote_char = ch;
614 current.push(ch);
615 } else if ch == ';' {
616 parts.push(std::mem::take(&mut current));
617 } else {
618 current.push(ch);
619 }
620 }
621
622 if !current.is_empty() {
623 parts.push(current);
624 }
625
626 parts
627}
628
629#[cfg(test)]
634mod tests {
635 use super::*;
636
637 fn dummy(_: &Command) {}
638
639 #[test]
640 fn common_command() {
641 let table: &[(&str, Handler)] = &[("*IDN?", dummy), ("*RST", dummy)];
642 let set = CommandSet::from_table(table).unwrap();
643
644 let cmds = set.parse("*IDN?").unwrap();
645 assert_eq!(cmds.len(), 1);
646 assert_eq!(cmds[0].index, 0);
647 assert!(cmds[0].params.is_empty());
648
649 let cmds = set.parse("*RST").unwrap();
650 assert_eq!(cmds[0].index, 1);
651 }
652
653 #[test]
654 fn common_with_param() {
655 let table: &[(&str, Handler)] = &[("*ESE num", dummy)];
656 let set = CommandSet::from_table(table).unwrap();
657
658 let cmds = set.parse("*ESE 42").unwrap();
659 assert_eq!(cmds[0].index, 0);
660 assert!(matches!(cmds[0].params[0], Param::Numeric(v) if v == 42.0));
661 }
662
663 #[test]
664 fn hierarchical_short_long() {
665 let table: &[(&str, Handler)] = &[("SYSTem:VERSion?", dummy)];
666 let set = CommandSet::from_table(table).unwrap();
667
668 assert!(set.parse("SYST:VERS?").is_ok());
670 assert!(set.parse("system:version?").is_ok());
672 assert!(set.parse("System:Version?").is_ok());
674 }
675
676 #[test]
677 fn optional_node() {
678 let table: &[(&str, Handler)] = &[("SYSTem:ERRor[:NEXT]?", dummy)];
679 let set = CommandSet::from_table(table).unwrap();
680
681 assert!(set.parse("SYST:ERR:NEXT?").is_ok());
683 assert!(set.parse("SYST:ERR?").is_ok());
685 }
686
687 #[test]
688 fn numeric_suffix() {
689 let table: &[(&str, Handler)] = &[("SOURce#:FREQuency num", dummy)];
690 let set = CommandSet::from_table(table).unwrap();
691
692 let cmds = set.parse("SOUR2:FREQ 1e6").unwrap();
693 assert_eq!(cmds[0].suffixes[0], 2);
694 assert!(matches!(cmds[0].params[0], Param::Numeric(v) if v == 1e6));
695
696 let cmds = set.parse("SOURCE:FREQ 500").unwrap();
698 assert_eq!(cmds[0].suffixes[0], 1);
699 }
700
701 #[test]
702 fn bool_param() {
703 let table: &[(&str, Handler)] = &[("OUTPut#:STATe bool", dummy)];
704 let set = CommandSet::from_table(table).unwrap();
705
706 let cmds = set.parse("OUTP1:STAT ON").unwrap();
707 assert!(matches!(cmds[0].params[0], Param::Bool(true)));
708
709 let cmds = set.parse("OUTPUT2:STATE 0").unwrap();
710 assert!(matches!(cmds[0].params[0], Param::Bool(false)));
711 assert_eq!(cmds[0].suffixes[0], 2);
712 }
713
714 #[test]
715 fn string_param() {
716 let table: &[(&str, Handler)] = &[("DISPlay:TEXT str", dummy)];
717 let set = CommandSet::from_table(table).unwrap();
718
719 let cmds = set.parse("DISP:TEXT \"hello world\"").unwrap();
720 assert!(matches!(&cmds[0].params[0], Param::String(s) if s == "hello world"));
721 }
722
723 #[test]
724 fn multi_command_line() {
725 let table: &[(&str, Handler)] = &[("*RST", dummy), ("*IDN?", dummy)];
726 let set = CommandSet::from_table(table).unwrap();
727
728 let cmds = set.parse("*RST;*IDN?").unwrap();
729 assert_eq!(cmds.len(), 2);
730 assert_eq!(cmds[0].index, 0);
731 assert_eq!(cmds[1].index, 1);
732 }
733
734 #[test]
735 fn optional_voltage_level() {
736 let table: &[(&str, Handler)] = &[
737 ("SOURce#:VOLTage[:LEVel] num", dummy),
738 ("SOURce#:VOLTage[:LEVel]?", dummy),
739 ];
740 let set = CommandSet::from_table(table).unwrap();
741
742 assert!(set.parse("SOUR1:VOLT:LEV 3.3").is_ok());
744 assert!(set.parse("SOUR1:VOLT 3.3").is_ok());
746 assert!(set.parse("SOUR1:VOLT:LEVEL?").is_ok());
748 assert!(set.parse("SOUR1:VOLT?").is_ok());
750 }
751}