Skip to main content

gridline_engine/engine/
cell_ref.rs

1use regex::Regex;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5/// A reference to a cell by row and column indices (0-indexed).
6#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
7pub struct CellRef {
8    pub row: usize,
9    pub col: usize,
10}
11
12impl CellRef {
13    pub fn new(row: usize, col: usize) -> CellRef {
14        CellRef { row, col }
15    }
16
17    /// Parse a cell reference from spreadsheet notation (e.g., "A1", "B2", "AA10").
18    /// Returns None if the input is invalid.
19    #[allow(clippy::should_implement_trait)]
20    pub fn from_str(name: &str) -> Option<CellRef> {
21        Self::parse_a1(name)
22    }
23
24    fn parse_a1(name: &str) -> Option<CellRef> {
25        let re = Regex::new(r"^(?<letters>[A-Za-z]+)(?<numbers>[0-9]+)$").unwrap();
26        let caps = re.captures(name)?;
27        let letters = &caps["letters"];
28        let numbers = &caps["numbers"];
29
30        let col = letters
31            .to_ascii_uppercase()
32            .bytes()
33            .fold(0usize, |acc, c| acc * 26 + (c - b'A') as usize + 1)
34            - 1;
35
36        let row = numbers.parse::<usize>().ok()?.checked_sub(1)?;
37
38        Some(CellRef::new(row, col))
39    }
40
41    /// Convert column index to spreadsheet-style letters (0 -> A, 25 -> Z, 26 -> AA).
42    pub fn col_to_letters(col: usize) -> String {
43        let mut result = String::new();
44        let mut n = col + 1;
45        while n > 0 {
46            n -= 1;
47            result.insert(0, (b'A' + (n % 26) as u8) as char);
48            n /= 26;
49        }
50        result
51    }
52}
53
54impl std::str::FromStr for CellRef {
55    type Err = String;
56
57    fn from_str(s: &str) -> Result<Self, Self::Err> {
58        Self::parse_a1(s).ok_or_else(|| format!("Invalid cell reference: {}", s))
59    }
60}
61
62impl fmt::Display for CellRef {
63    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64        write!(f, "{}{}", CellRef::col_to_letters(self.col), self.row + 1)
65    }
66}