command_engine/shared/
instruction.rs1use std::collections::HashMap;
2use crate::Error;
3
4const FLAG_PREFIX: &str = "--";
5const SYMBOL_SPACE: char = ' ';
6const SYMBOL_QUOTES: char = '"';
7const SYMBOL_HASH: char = '#';
8
9#[derive(Default, Debug)]
10struct State<'a> {
11 buffer: Vec<&'a str>,
12 start: Option<usize>,
13 end: Option<usize>,
14 ignore_space: bool,
15 collecting: bool,
16 previous: Option<char>,
17}
18
19impl<'a> State<'a> {
20 fn push_part(&mut self, pos: usize, input: &'a str) {
21 if self.start.is_none() {
22 return;
23 }
24
25 let start = self.start.take().unwrap();
26 let end = *self.end.insert(pos);
27 let part = &input[start..end];
28
29 self.buffer.push(part);
30 }
31}
32
33#[derive(Debug, Eq, PartialEq)]
51pub struct Instruction<'a> {
52 pub caller: &'a str,
53 pub args: Vec<&'a str>,
54 pub o_args: HashMap<&'a str, Option<Vec<&'a str>>>,
55 pub input: &'a str,
56}
57
58impl<'a> Instruction<'a> {
59 pub fn new<T: AsRef<str> + ?Sized>(input: &'a T) -> Result<Instruction<'a>, Error> {
60 let mut instruction = Self::empty();
61 instruction.input = input.as_ref();
62
63 let input = instruction.input;
64
65 let mut state = State::default();
66 for (pos, char) in input.chars().enumerate() {
67 if state.collecting || state.ignore_space || char != SYMBOL_SPACE {
68 if !state.collecting && char == SYMBOL_QUOTES {
69 if state.previous == Some(SYMBOL_HASH) {
70 state.collecting = true;
71 } else {
72 if state.ignore_space {
73 state.push_part(pos, input);
74 state.ignore_space = false
75 } else {
76 state.ignore_space = true;
77 }
78 }
79 } else {
80 if state.collecting && char == SYMBOL_HASH && state.previous == Some(SYMBOL_QUOTES) {
81 state.collecting = false;
82 state.start = state.start.map(|pos| pos+2);
83 state.push_part(pos-1, input);
84 } else {
85 if state.start.is_none() {
86 state.start = Some(pos);
87 } else {
88 let _ = state.end.insert(pos);
89 }
90 }
91 }
92 } else {
93 state.push_part(pos, input);
94 }
95
96 state.previous = Some(char);
97 }
98
99 if let Some(start) = state.start {
100 if let Some(end) = state.end {
101 let part = &input[start..=end];
102 if !part.is_empty() {
103 state.buffer.push(part);
104 }
105 }
106 }
107
108 let mut split = state.buffer.into_iter();
109
110 if let Some(part) = split.next() {
111 instruction.caller = part;
112 } else {
113 return Err(Error::InstructionMissingCaller);
114 }
115
116 let mut current_o_arg = "#";
117 let mut is_pos_args = true;
118 for part in split {
119 if is_pos_args {
120 if part.starts_with(FLAG_PREFIX) {
121 is_pos_args = false;
122 } else {
123 instruction.args.push(part);
124 }
125 }
126
127 if !is_pos_args {
128 if part.starts_with(FLAG_PREFIX) {
129 instruction.o_args.insert(part, None);
130 current_o_arg = part;
131 } else {
132 let sub_args = instruction
133 .o_args
134 .get_mut(current_o_arg)
135 .ok_or_else(|| Error::InstructionSubArgWithoutOArg)?;
136
137 if let Some(sub_args) = sub_args {
138 sub_args.push(part);
139 } else {
140 *sub_args = Some(vec![part]);
141 }
142 }
143 }
144 }
145
146 Ok(instruction)
147 }
148
149 fn empty() -> Instruction<'a> {
150 Self {
151 caller: "",
152 args: Vec::new(),
153 o_args: HashMap::new(),
154 input: "",
155 }
156 }
157}
158
159#[cfg(test)]
160impl Default for Instruction<'_> {
161 fn default() -> Self {
162 Self::empty()
163 }
164}