use std::fmt::Display;
use crate::{
operation::{Operation, RegisterOperation},
Error,
};
pub(crate) struct RegisterDefinition {
pub name: char,
pub contents: RegisterContents,
}
pub(crate) enum RegisterContents {
Size(u16),
Values(Vec<u16>),
}
#[derive(Debug, Copy, Clone)]
pub(crate) struct Line {
pub label: Option<u32>,
pub instruction: Instruction,
}
impl Display for Line {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let (start, op, end) = match self.instruction {
Instruction::Register(name, op, val) => (
Some(name.to_string()),
Operation::Register(op),
Some(format!("{val}")),
),
Instruction::Result(val) => match val {
ResultValue::All => (None, Operation::Result, None),
ResultValue::Register(name) => (Some(name.to_string()), Operation::Result, None),
ResultValue::Value(val) => (None, Operation::Result, Some(format!("{val}"))),
},
Instruction::Jump(reg, label) => (
reg.map(|c| c.to_string()),
Operation::Jump,
Some(format!("{label}")),
),
Instruction::Take(to, from) => (
Some(from.to_string()),
Operation::Take,
Some(to.to_string()),
),
};
write!(
f,
"{}{}{}{}",
self.label.map(|s| s.to_string()).unwrap_or_default(),
start.unwrap_or_default(),
op.to_char(),
end.unwrap_or_default(),
)
}
}
pub(crate) type RegisterName = char;
#[derive(Debug, Copy, Clone)]
pub(crate) enum Instruction {
Register(RegisterName, RegisterOperation, Value),
Result(ResultValue),
Jump(Option<RegisterName>, u32),
Take(RegisterName, RegisterName),
}
#[derive(Debug, Copy, Clone)]
pub(crate) enum ResultValue {
All,
Register(RegisterName),
Value(Value),
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum Value {
Register(RegisterName),
Number(u16),
}
impl Display for Value {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Value::Register(name) => write!(f, "{name}"),
Value::Number(val) => write!(f, "{val}"),
}
}
}
fn strip_comments(line: &str) -> &str {
&line[0..line.find(';').unwrap_or(line.len())]
}
/// Parse an instruction and add it to the `bytecode` list.
fn parse_instruction(line: &str) -> Result<Line, String> {
let mut chars = line.chars().peekable();
let mut numerics = Vec::new();
while let Some(number) = &chars.peek() {
if !number.is_numeric() {
break;
}
numerics.push(**number);
chars.next();
}
let label = numerics.into_iter().collect::<String>().parse().ok();
let ins_or_reg = chars.next().unwrap();
let (left, op) = match Operation::try_from_char(ins_or_reg) {
// Begins with operation.
Some(code) => (None, code),
// Begins with register.
None => {
let op = chars
.next()
.ok_or_else(|| "Expected operation".to_string())?;
(
Some(ins_or_reg),
Operation::try_from_char(op).ok_or_else(|| format!("Invalid operation: {op}"))?,
)
}
};
// Parse the end: could be a number or a register.
let right = chars
.clone()
.collect::<String>()
.parse()
.map(|num| Some(Value::Number(num)))
.unwrap_or_else(|_| chars.next().map(Value::Register));
if let Operation::Jump = op {
if let Some(Value::Register(reg)) = right {
return Err(format!(
"Expected label for jump instruction, got register: {reg}"
));
};
};
Ok(Line {
label,
instruction: match op {
Operation::Register(regop) => Instruction::Register(
left.ok_or(format!("Expected register for {} instruction", op))?,
regop,
right.unwrap_or(Value::Number(1)),
),
Operation::Result => Instruction::Result(if let Some(reg) = left {
ResultValue::Register(reg)
} else if let Some(val) = right {
ResultValue::Value(val)
} else {
ResultValue::All
}),
Operation::Jump => Instruction::Jump(
left,
match right {
Some(Value::Number(num)) => Ok(num as u32),
Some(Value::Register(reg)) => Err(format!(
"Expected label for jump instruction, got register {reg}"
)),
None => Err("Expected label for jump instruction".to_string()),
}?,
),
Operation::Take => Instruction::Take(
left.ok_or(format!("Expected register for {} instruction", op))?,
match right {
Some(Value::Register(reg)) => Ok(reg),
_ => Err("Expected register for take instruction".to_string()),
}?,
),
},
})
}
/// Parse a line adding a register to the program.
/// Can be one of two formats:
/// Specifying the length: `a:1`
/// Specifying the content: `a:[1 2 3]`
fn parse_register_definition(line: &str) -> Result<RegisterDefinition, String> {
let mut line = line.chars();
let name = line.next().unwrap();
let _column = line.next().unwrap();
let def: String = line.collect();
Ok(RegisterDefinition {
name,
contents: if let Some(list) = def.strip_prefix('[') {
RegisterContents::Values(
list.strip_suffix(']')
.ok_or_else(|| "Expected closing bracket".to_string())?
.split_whitespace()
.map(|n| n.parse::<u16>())
.collect::<Result<Vec<u16>, _>>()
.map_err(|e| format!("Couldn't parse number: {e}"))?,
)
} else {
RegisterContents::Size(
def.parse::<u16>()
.map_err(|e| format!("Couldn't parse register size '{def}': {e}"))?,
)
},
})
}
pub(crate) type RegisterDefinitions = Vec<(usize, RegisterDefinition)>;
pub(crate) fn parse(source: &str) -> Result<(Vec<(usize, Line)>, RegisterDefinitions), Error> {
let mut lines = Vec::new();
let mut register_definitions = Vec::new();
for (line_num, line) in source.lines().enumerate() {
let line = strip_comments(line).trim();
if line.is_empty() {
continue;
};
if line.contains(':') {
register_definitions.push((
line_num,
parse_register_definition(line).map_err(|message| Error {
message,
line: line_num,
})?,
))
} else {
lines.push((
line_num + 1,
parse_instruction(line).map_err(|message| Error {
message,
line: line_num,
})?,
))
}
}
Ok((lines, register_definitions))
}