logisheets_controller 0.6.0

the core of LogiSheets
Documentation
use super::{CalcValue, CalcVertex, Value};
use crate::calc_engine::{
    calculator::calc_vertex::{CalcReference, Reference},
    connector::Connector,
};
use logisheets_base::{column_label_to_index, Addr};
use logisheets_parser::ast;
use regex::Regex;

pub fn calc<C>(args: Vec<CalcVertex>, fetcher: &mut C) -> CalcVertex
where
    C: Connector,
{
    assert_or_return!(args.len() >= 1 && args.len() <= 2, ast::Error::Unspecified);
    let mut args_iter = args.into_iter();
    let first = fetcher.get_calc_value(args_iter.next().unwrap());
    assert_text_from_calc_value!(r, first);
    let a1_ref = if let Some(arg) = args_iter.next() {
        assert_bool_from_calc_value!(b, fetcher.get_calc_value(arg));
        b
    } else {
        true
    };
    let result = if a1_ref {
        parse_a1_ref(&r)
    } else {
        parse_r1c1_ref(&r)
    };
    assert_or_return!(result.is_some(), ast::Error::Value);

    let (sheet_name, row_idx, col_idx) = result.unwrap();
    let mut sheet_id = fetcher.get_active_sheet();
    if let Some(name) = sheet_name {
        if let Ok(id) = fetcher.get_sheet_id_by_name(&name) {
            sheet_id = id;
        }
    };
    CalcVertex::Reference(CalcReference {
        from_sheet: None,
        sheet: sheet_id,
        reference: Reference::Addr(Addr {
            row: row_idx,
            col: col_idx,
        }),
    })
}

fn parse_a1_ref(s: &str) -> Option<(Option<String>, usize, usize)> {
    let r = Regex::new(r#"((.+)!)?\$?([A-Z]+)\$?([0-9]+)"#).unwrap();
    let capture = r.captures_iter(s).next()?;
    let sheet_name = if let Some(s) = capture.get(2) {
        Some(s.as_str().to_string())
    } else {
        None
    };
    let col_str = capture.get(3)?;
    let row_str = capture.get(4)?;
    let col = column_label_to_index(col_str.as_str());
    let row = row_str.as_str().parse::<usize>().ok()? - 1;
    Some((sheet_name, row, col))
}

fn parse_r1c1_ref(s: &str) -> Option<(Option<String>, usize, usize)> {
    let r = Regex::new(r#"((.+?)!)[Rr]([0-9]+)[Cc]([0-9]+)"#).unwrap();
    let capture = r.captures_iter(s).next()?;
    let sheet_name = if let Some(s) = capture.get(2) {
        Some(s.as_str().to_string())
    } else {
        None
    };
    let row_str = capture.get(3)?;
    let col_str = capture.get(4)?;
    let row = row_str.as_str().parse::<usize>().ok()? - 1;
    let col = col_str.as_str().parse::<usize>().ok()? - 1;
    Some((sheet_name, row, col))
}

#[cfg(test)]
mod tests {
    use super::parse_a1_ref;

    #[test]
    fn test_a1_ref() {
        let r1 = "L2";
        parse_a1_ref(r1).unwrap();
        let r2 = "$L2";
        parse_a1_ref(r2).unwrap();
        let r3 = "$L$2";
        parse_a1_ref(r3).unwrap();
        let r4 = "L$2";
        parse_a1_ref(r4).unwrap();
        let r5 = "Sheet1!L$2";
        let (s, _, _) = parse_a1_ref(r5).unwrap();
        assert_eq!(s.unwrap(), "Sheet1");
    }
}