use super::token::{Operand, OperandKind, Operator, RowOperation};
use crate::error::MatrixError;
pub fn parse_operand(s: &str) -> Result<Operand, MatrixError> {
let s = s.trim();
if s.is_empty() {
return Err(MatrixError::ParseError {
line: 0,
message: "empty operand".into(),
});
}
if let Some(kind) = try_parse_ref(s) {
return Ok(Operand {
kind,
raw: s.to_string(),
});
}
if let Some(slash) = s.find('/') {
let num = s[..slash]
.trim()
.parse::<i64>()
.map_err(|_| MatrixError::ParseError {
line: 0,
message: format!("invalid numerator: {}", &s[..slash]),
})?;
let den = s[slash + 1..]
.trim()
.parse::<i64>()
.map_err(|_| MatrixError::ParseError {
line: 0,
message: format!("invalid denominator: {}", &s[slash + 1..]),
})?;
return Ok(Operand::fraction(num, den, s));
}
if let Ok(n) = s.parse::<f64>() {
return Ok(Operand::number(n, s));
}
Ok(Operand::variable(s))
}
fn try_parse_ref(s: &str) -> Option<OperandKind> {
let s = s.trim();
let lower = s.to_lowercase();
let is_row = lower.starts_with('r');
let is_col = lower.starts_with('c');
if !is_row && !is_col {
return None;
}
if s.len() > 2 && s.chars().nth(1) == Some('[') && s.ends_with(']') {
let inner = &s[2..s.len() - 1];
return inner.parse::<usize>().ok().map(|n| {
if is_row {
OperandKind::RowRef(n)
} else {
OperandKind::ColRef(n)
}
});
}
if s.len() > 1 {
let rest = &s[1..].trim_start_matches('[').trim_end_matches(']');
return rest.parse::<usize>().ok().map(|n| {
if is_row {
OperandKind::RowRef(n)
} else {
OperandKind::ColRef(n)
}
});
}
None
}
pub fn parse_operation(input: &str, target: usize) -> Result<RowOperation, MatrixError> {
let input = input.trim();
if input.is_empty() {
return Err(MatrixError::ParseError {
line: 0,
message: "empty operation".into(),
});
}
let first = input.chars().next().unwrap();
let operator = match first {
'+' => Operator::Add,
'-' => Operator::Subtract,
'x' | 'X' | '*' => Operator::Multiply,
'<' => Operator::Replace,
_ => {
return Err(MatrixError::ParseError {
line: 0,
message: format!("unknown operator: '{}'", first),
});
}
};
let rest = input[1..].trim();
let raw: Vec<&str> = rest.split_whitespace().collect();
let mut parts: Vec<String> = Vec::new();
let mut i = 0;
while i < raw.len() {
if (raw[i].eq_ignore_ascii_case("r") || raw[i].eq_ignore_ascii_case("c"))
&& i + 1 < raw.len()
&& raw[i + 1].starts_with('[')
{
parts.push(format!("{}{}", raw[i], raw[i + 1]));
i += 2;
} else {
parts.push(raw[i].to_string());
i += 1;
}
}
let mut operands = Vec::new();
for part in &parts {
operands.push(parse_operand(part)?);
}
Ok(RowOperation {
target,
operator,
operands,
})
}
#[cfg(test)]
mod tests {
use super::*;
use crate::math::token::OperandKind;
#[test]
fn test_row_ref_1_indexed() {
assert!(matches!(
parse_operand("r[1]").unwrap().kind,
OperandKind::RowRef(1)
));
assert!(matches!(
parse_operand("r[2]").unwrap().kind,
OperandKind::RowRef(2)
));
}
#[test]
fn test_col_ref() {
assert!(matches!(
parse_operand("c[1]").unwrap().kind,
OperandKind::ColRef(1)
));
assert!(matches!(
parse_operand("c[3]").unwrap().kind,
OperandKind::ColRef(3)
));
}
#[test]
fn test_row_ref_with_space() {
let op = parse_operation("- r [1]", 1).unwrap();
assert_eq!(op.operator, Operator::Subtract);
assert_eq!(op.source_row(), Some(1));
}
#[test]
fn test_fraction() {
let op = parse_operand("1/2").unwrap();
assert!(matches!(op.kind, OperandKind::Fraction(_)));
assert!((op.to_f64() - 0.5).abs() < 1e-10);
}
#[test]
fn test_add_operation() {
let op = parse_operation("+ 2 r[1]", 0).unwrap();
assert_eq!(op.operator, Operator::Add);
assert_eq!(op.source_row(), Some(1));
}
}