1use crate::Value;
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
28pub enum AsmRole {
29 Output,
31 Input,
33}
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct AsmOperand {
38 pub role: AsmRole,
40 pub memory: bool,
42 pub result: Option<Value>,
44 pub value: Option<Value>,
50 pub tied: Option<usize>,
55}
56
57#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct AsmOperands {
60 list: Vec<AsmOperand>,
61}
62
63impl AsmOperands {
64 #[must_use]
69 pub fn read(constraints: &str, results: &[Value], values: &[Value]) -> Option<AsmOperands> {
70 let written: Vec<&str> =
73 if constraints.is_empty() { Vec::new() } else { constraints.split(',').collect() };
74
75 let mut list = Vec::with_capacity(written.len());
76 let mut result = results.iter();
77 let mut value = values.iter();
78 for text in written {
79 let entry = Entry::read(text)?;
80 let register = entry.role == AsmRole::Output && !entry.memory;
83 let taken = if register { Some(result.next().copied()?) } else { None };
84 let read = if register && !entry.updates { None } else { Some(value.next().copied()?) };
85 list.push(AsmOperand {
86 role: entry.role,
87 memory: entry.memory,
88 result: taken,
89 value: read,
90 tied: entry.tied,
91 });
92 }
93
94 if result.next().is_some() || value.next().is_some() {
97 return None;
98 }
99 for entry in &list {
102 if let Some(tied) = entry.tied {
103 if !list.get(tied).is_some_and(|output| output.result.is_some()) {
104 return None;
105 }
106 }
107 }
108 Some(AsmOperands { list })
109 }
110
111 pub fn iter(&self) -> impl Iterator<Item = &AsmOperand> {
113 self.list.iter()
114 }
115
116 #[must_use]
121 pub fn tied_to(&self, output: usize) -> Option<Value> {
122 let written = self.list.get(output)?;
123 if written.value.is_some() {
124 return written.value;
125 }
126 self.list.iter().find(|entry| entry.tied == Some(output)).and_then(|entry| entry.value)
127 }
128
129 #[must_use]
131 pub fn len(&self) -> usize {
132 self.list.len()
133 }
134
135 #[must_use]
137 pub fn is_empty(&self) -> bool {
138 self.list.is_empty()
139 }
140}
141
142#[derive(Debug, Clone, Copy, PartialEq, Eq)]
144struct Entry {
145 role: AsmRole,
146 memory: bool,
147 updates: bool,
148 tied: Option<usize>,
149}
150
151impl Entry {
152 fn read(text: &str) -> Option<Entry> {
158 let mut role = AsmRole::Input;
159 let mut updates = false;
160 let mut memory = false;
161 let mut register = false;
162 let mut tied = None;
163
164 let mut rest = text.chars().peekable();
165 while let Some(letter) = rest.next() {
166 match letter {
167 '=' => role = AsmRole::Output,
168 '+' => {
169 role = AsmRole::Output;
170 updates = true;
171 }
172 '&' | '%' => {}
176 'm' | 'o' | 'V' => memory = true,
179 'r' | 'g' | 'X' | 'i' | 'n' | 's' | 'a' | 'b' | 'c' | 'd' | 'S' | 'D' | 'A'
183 | 'q' | 'Q' | 'f' | 't' | 'u' | 'x' | 'y' | 'v' | 'l' | 'e' | 'k' | 'h' | 'j'
184 | 'z' | 'w' => register = true,
185 'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' => {
188 register = true;
189 }
190 '0'..='9' => {
194 let mut number = letter.to_digit(10)? as usize;
195 while let Some(next) = rest.peek().and_then(|&c| c.to_digit(10)) {
196 number = number * 10 + next as usize;
197 rest.next();
198 }
199 tied = Some(number);
200 register = true;
201 }
202 _ => return None,
203 }
204 }
205
206 if !memory && !register {
209 return None;
210 }
211 Some(Entry { role, memory: memory && !register, updates, tied })
212 }
213}
214
215#[cfg(test)]
216mod tests {
217 use super::*;
218 use crate::Value;
219
220 fn values(count: usize) -> Vec<Value> {
222 (0..count).map(Value::from_usize).collect()
223 }
224
225 #[test]
226 fn a_statement_with_no_operands_has_none() {
227 let read =
228 AsmOperands::read("", &[], &[]).expect("an empty list describes an empty statement");
229 assert!(read.is_empty());
230 }
231
232 #[test]
233 fn an_input_in_a_register_reads_a_value_and_produces_nothing() {
234 let values = values(1);
235 let read = AsmOperands::read("r", &[], &values).expect("one input");
236 let [operand] = read.iter().copied().collect::<Vec<_>>()[..] else { panic!("one operand") };
237 assert_eq!(operand.role, AsmRole::Input);
238 assert!(!operand.memory);
239 assert_eq!(operand.result, None);
240 assert_eq!(operand.value, Some(values[0]));
241 }
242
243 #[test]
244 fn an_output_in_a_register_produces_a_result_and_reads_nothing() {
245 let results = values(1);
246 let read = AsmOperands::read("=r", &results, &[]).expect("one output");
247 let operand = read.iter().next().copied().expect("one operand");
248 assert_eq!(operand.role, AsmRole::Output);
249 assert_eq!(operand.result, Some(results[0]));
250 assert_eq!(operand.value, None);
251 assert_eq!(read.tied_to(0), None);
252 }
253
254 #[test]
255 fn an_output_written_plus_reads_the_value_it_overwrites() {
256 let results = values(1);
257 let args = values(1);
258 let read = AsmOperands::read("+r", &results, &args).expect("one output read and written");
259 let operand = read.iter().next().copied().expect("one operand");
260 assert_eq!(operand.result, Some(results[0]));
261 assert_eq!(operand.value, Some(args[0]));
262 assert_eq!(read.tied_to(0), Some(args[0]));
263 }
264
265 #[test]
266 fn an_input_written_as_a_number_shares_the_place_of_that_output() {
267 let results = values(1);
268 let args = values(1);
269 let read = AsmOperands::read("=r,0", &results, &args).expect("an output and its match");
270 let list: Vec<AsmOperand> = read.iter().copied().collect();
271 assert_eq!(list[1].tied, Some(0));
272 assert_eq!(read.tied_to(0), Some(args[0]));
273 }
274
275 #[test]
276 fn an_operand_in_memory_is_an_address_whichever_side_it_is_on() {
277 let args = values(2);
278 let read = AsmOperands::read("=m,m", &[], &args).expect("an output and an input in memory");
279 let list: Vec<AsmOperand> = read.iter().copied().collect();
280 assert!(list[0].memory && list[1].memory);
281 assert_eq!(list[0].result, None);
282 assert_eq!(list[0].value, Some(args[0]));
283 assert_eq!(list[1].value, Some(args[1]));
284 }
285
286 #[test]
287 fn a_constraint_that_allows_a_register_or_memory_takes_the_register() {
288 let results = values(1);
289 let read = AsmOperands::read("=rm", &results, &[]).expect("an output that could be either");
290 let operand = read.iter().next().copied().expect("one operand");
291 assert!(!operand.memory);
292 assert_eq!(operand.result, Some(results[0]));
293 }
294
295 #[test]
296 fn a_list_describing_more_results_than_there_are_is_refused() {
297 assert_eq!(AsmOperands::read("=r,=r", &values(1), &[]), None);
298 }
299
300 #[test]
301 fn a_list_describing_fewer_values_than_there_are_is_refused() {
302 assert_eq!(AsmOperands::read("r", &[], &values(2)), None);
303 }
304
305 #[test]
306 fn a_number_naming_an_output_that_is_not_there_is_refused() {
307 assert_eq!(AsmOperands::read("=r,3", &values(1), &values(1)), None);
308 }
309
310 #[test]
311 fn a_number_naming_an_output_in_memory_is_refused() {
312 assert_eq!(AsmOperands::read("=m,0", &[], &values(2)), None);
313 }
314
315 #[test]
316 fn a_letter_nobody_has_read_is_refused() {
317 assert_eq!(AsmOperands::read("^", &[], &values(1)), None);
318 }
319
320 #[test]
321 fn a_constraint_that_names_nowhere_at_all_is_refused() {
322 assert_eq!(AsmOperands::read("&", &[], &values(1)), None);
323 }
324
325 #[test]
326 fn a_number_of_more_than_one_digit_is_the_whole_run() {
327 let results = values(11);
328 let args = values(1);
329 let outputs = ["=r"; 11].join(",");
330 let read = AsmOperands::read(&format!("{outputs},10"), &results, &args)
331 .expect("eleven outputs and a match on the last");
332 let list: Vec<AsmOperand> = read.iter().copied().collect();
333 assert_eq!(list[11].tied, Some(10));
334 assert_eq!(read.tied_to(10), Some(args[0]));
335 }
336}