elementary-row-operation-verifier 0.0.1

A tool to verify the correctness of elementary row operations on matrices
Documentation
//! 数学表达式解析 — 行列操作

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;
    }
    // r[n], c[n]
    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)
            }
        });
    }
    // r n] or r 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));
    }
}