elementary-row-operation-verifier 0.0.1

A tool to verify the correctness of elementary row operations on matrices
Documentation
//! 统一块解析:定位行 → 列定义 → 分支解析

use crate::error::MatrixError;
use crate::parser::common::{ColDef, col_widths, parse_marker, slice_all};
use crate::parser::types::RawBlock;

/// 解析一个矩阵块
pub fn parse_block(lines: &[&str]) -> Result<RawBlock, MatrixError> {
    let defs = parse_marker(lines[0])?;
    let has_op_col = defs.iter().any(|d| !d.is_num);
    if has_op_col {
        parse_row_block(lines, &defs)
    } else {
        parse_col_block(lines, &defs)
    }
}

/// 行变换:操作在最后一列,每行一个
fn parse_row_block(lines: &[&str], defs: &[ColDef]) -> Result<RawBlock, MatrixError> {
    let ncols = defs.iter().filter(|d| d.is_num).count();
    let op_col_idx = defs.iter().position(|d| !d.is_num);
    let mut data_rows = vec![];
    let mut ops = vec![];

    for line in &lines[1..] {
        if line.trim().is_empty() {
            continue;
        }
        let slices = slice_all(line, defs);
        let nums: Vec<f64> = slices
            .iter()
            .take(ncols)
            .filter_map(|s| s.parse::<f64>().ok())
            .collect();
        if nums.len() == ncols {
            data_rows.push(nums);
            ops.push(
                slices
                    .get(op_col_idx.unwrap_or(0))
                    .cloned()
                    .unwrap_or_default(),
            );
        }
    }

    Ok(RawBlock {
        rows: data_rows,
        ops,
        op_slices: vec![],
        col_widths: col_widths(defs),
    })
}

/// 列变换:操作在数据行下方,| 链追踪
fn parse_col_block(lines: &[&str], defs: &[ColDef]) -> Result<RawBlock, MatrixError> {
    let ncols = defs.iter().filter(|d| d.is_num).count();
    let mut data_rows = vec![];
    let mut op_rows: Vec<Vec<String>> = vec![];

    for line in &lines[1..] {
        if line.trim().is_empty() || line.trim() == "_" {
            continue;
        }
        let slices = slice_all(line, defs);
        let nums: Vec<f64> = slices
            .iter()
            .take(ncols)
            .filter_map(|s| s.parse::<f64>().ok())
            .collect();
        if nums.len() == ncols && op_rows.is_empty() {
            data_rows.push(nums);
        } else {
            op_rows.push(slices);
        }
    }

    let mut ops = vec![String::new(); ncols];
    for ci in 0..ncols {
        let mut op = String::new();
        for row in &op_rows {
            let cell = row.get(ci).map(|s| s.as_str()).unwrap_or("");
            if cell == "|" {
                continue;
            }
            if !cell.is_empty() {
                op = cell.to_string();
                break;
            }
        }
        ops[ci] = op;
    }

    Ok(RawBlock {
        rows: data_rows,
        ops,
        op_slices: op_rows,
        col_widths: col_widths(defs),
    })
}

pub fn detect_kind(lines: &[&str]) -> crate::parser::OpKind {
    use crate::parser::OpKind;
    if lines.is_empty() {
        return OpKind::Row;
    }
    let first = lines[0];
    let has_op_col = first.chars().any(|c| c == '|' || c == '_');
    let has_trail = lines.iter().any(|l| l.trim() == "_");
    match (has_op_col, has_trail) {
        (true, _) => OpKind::Row,
        (false, true) => OpKind::Col,
        _ => OpKind::Row,
    }
}