use crate::Value;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum AsmRole {
Output,
Input,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct AsmOperand {
pub role: AsmRole,
pub memory: bool,
pub result: Option<Value>,
pub value: Option<Value>,
pub tied: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AsmOperands {
list: Vec<AsmOperand>,
}
impl AsmOperands {
#[must_use]
pub fn read(constraints: &str, results: &[Value], values: &[Value]) -> Option<AsmOperands> {
let written: Vec<&str> =
if constraints.is_empty() { Vec::new() } else { constraints.split(',').collect() };
let mut list = Vec::with_capacity(written.len());
let mut result = results.iter();
let mut value = values.iter();
for text in written {
let entry = Entry::read(text)?;
let register = entry.role == AsmRole::Output && !entry.memory;
let taken = if register { Some(result.next().copied()?) } else { None };
let read = if register && !entry.updates { None } else { Some(value.next().copied()?) };
list.push(AsmOperand {
role: entry.role,
memory: entry.memory,
result: taken,
value: read,
tied: entry.tied,
});
}
if result.next().is_some() || value.next().is_some() {
return None;
}
for entry in &list {
if let Some(tied) = entry.tied {
if !list.get(tied).is_some_and(|output| output.result.is_some()) {
return None;
}
}
}
Some(AsmOperands { list })
}
pub fn iter(&self) -> impl Iterator<Item = &AsmOperand> {
self.list.iter()
}
#[must_use]
pub fn tied_to(&self, output: usize) -> Option<Value> {
let written = self.list.get(output)?;
if written.value.is_some() {
return written.value;
}
self.list.iter().find(|entry| entry.tied == Some(output)).and_then(|entry| entry.value)
}
#[must_use]
pub fn len(&self) -> usize {
self.list.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.list.is_empty()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Entry {
role: AsmRole,
memory: bool,
updates: bool,
tied: Option<usize>,
}
impl Entry {
fn read(text: &str) -> Option<Entry> {
let mut role = AsmRole::Input;
let mut updates = false;
let mut memory = false;
let mut register = false;
let mut tied = None;
let mut rest = text.chars().peekable();
while let Some(letter) = rest.next() {
match letter {
'=' => role = AsmRole::Output,
'+' => {
role = AsmRole::Output;
updates = true;
}
'&' | '%' => {}
'm' | 'o' | 'V' => memory = true,
'r' | 'g' | 'X' | 'i' | 'n' | 's' | 'a' | 'b' | 'c' | 'd' | 'S' | 'D' | 'A'
| 'q' | 'Q' | 'f' | 't' | 'u' | 'x' | 'y' | 'v' | 'l' | 'e' | 'k' | 'h' | 'j'
| 'z' | 'w' => register = true,
'E' | 'F' | 'G' | 'H' | 'I' | 'J' | 'K' | 'L' | 'M' | 'N' | 'O' | 'P' => {
register = true;
}
'0'..='9' => {
let mut number = letter.to_digit(10)? as usize;
while let Some(next) = rest.peek().and_then(|&c| c.to_digit(10)) {
number = number * 10 + next as usize;
rest.next();
}
tied = Some(number);
register = true;
}
_ => return None,
}
}
if !memory && !register {
return None;
}
Some(Entry { role, memory: memory && !register, updates, tied })
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Value;
fn values(count: usize) -> Vec<Value> {
(0..count).map(Value::from_usize).collect()
}
#[test]
fn a_statement_with_no_operands_has_none() {
let read =
AsmOperands::read("", &[], &[]).expect("an empty list describes an empty statement");
assert!(read.is_empty());
}
#[test]
fn an_input_in_a_register_reads_a_value_and_produces_nothing() {
let values = values(1);
let read = AsmOperands::read("r", &[], &values).expect("one input");
let [operand] = read.iter().copied().collect::<Vec<_>>()[..] else { panic!("one operand") };
assert_eq!(operand.role, AsmRole::Input);
assert!(!operand.memory);
assert_eq!(operand.result, None);
assert_eq!(operand.value, Some(values[0]));
}
#[test]
fn an_output_in_a_register_produces_a_result_and_reads_nothing() {
let results = values(1);
let read = AsmOperands::read("=r", &results, &[]).expect("one output");
let operand = read.iter().next().copied().expect("one operand");
assert_eq!(operand.role, AsmRole::Output);
assert_eq!(operand.result, Some(results[0]));
assert_eq!(operand.value, None);
assert_eq!(read.tied_to(0), None);
}
#[test]
fn an_output_written_plus_reads_the_value_it_overwrites() {
let results = values(1);
let args = values(1);
let read = AsmOperands::read("+r", &results, &args).expect("one output read and written");
let operand = read.iter().next().copied().expect("one operand");
assert_eq!(operand.result, Some(results[0]));
assert_eq!(operand.value, Some(args[0]));
assert_eq!(read.tied_to(0), Some(args[0]));
}
#[test]
fn an_input_written_as_a_number_shares_the_place_of_that_output() {
let results = values(1);
let args = values(1);
let read = AsmOperands::read("=r,0", &results, &args).expect("an output and its match");
let list: Vec<AsmOperand> = read.iter().copied().collect();
assert_eq!(list[1].tied, Some(0));
assert_eq!(read.tied_to(0), Some(args[0]));
}
#[test]
fn an_operand_in_memory_is_an_address_whichever_side_it_is_on() {
let args = values(2);
let read = AsmOperands::read("=m,m", &[], &args).expect("an output and an input in memory");
let list: Vec<AsmOperand> = read.iter().copied().collect();
assert!(list[0].memory && list[1].memory);
assert_eq!(list[0].result, None);
assert_eq!(list[0].value, Some(args[0]));
assert_eq!(list[1].value, Some(args[1]));
}
#[test]
fn a_constraint_that_allows_a_register_or_memory_takes_the_register() {
let results = values(1);
let read = AsmOperands::read("=rm", &results, &[]).expect("an output that could be either");
let operand = read.iter().next().copied().expect("one operand");
assert!(!operand.memory);
assert_eq!(operand.result, Some(results[0]));
}
#[test]
fn a_list_describing_more_results_than_there_are_is_refused() {
assert_eq!(AsmOperands::read("=r,=r", &values(1), &[]), None);
}
#[test]
fn a_list_describing_fewer_values_than_there_are_is_refused() {
assert_eq!(AsmOperands::read("r", &[], &values(2)), None);
}
#[test]
fn a_number_naming_an_output_that_is_not_there_is_refused() {
assert_eq!(AsmOperands::read("=r,3", &values(1), &values(1)), None);
}
#[test]
fn a_number_naming_an_output_in_memory_is_refused() {
assert_eq!(AsmOperands::read("=m,0", &[], &values(2)), None);
}
#[test]
fn a_letter_nobody_has_read_is_refused() {
assert_eq!(AsmOperands::read("^", &[], &values(1)), None);
}
#[test]
fn a_constraint_that_names_nowhere_at_all_is_refused() {
assert_eq!(AsmOperands::read("&", &[], &values(1)), None);
}
#[test]
fn a_number_of_more_than_one_digit_is_the_whole_run() {
let results = values(11);
let args = values(1);
let outputs = ["=r"; 11].join(",");
let read = AsmOperands::read(&format!("{outputs},10"), &results, &args)
.expect("eleven outputs and a match on the last");
let list: Vec<AsmOperand> = read.iter().copied().collect();
assert_eq!(list[11].tied, Some(10));
assert_eq!(read.tied_to(10), Some(args[0]));
}
}