gridline_engine/engine/
cell_ref.rs1use regex::Regex;
16use serde::{Deserialize, Serialize};
17use std::fmt;
18
19#[derive(Clone, Debug, Hash, Eq, PartialEq, Serialize, Deserialize)]
21pub struct CellRef {
22 pub row: usize,
23 pub col: usize,
24}
25
26impl CellRef {
27 pub fn new(row: usize, col: usize) -> CellRef {
28 CellRef { row, col }
29 }
30
31 #[allow(clippy::should_implement_trait)]
34 pub fn from_str(name: &str) -> Option<CellRef> {
35 Self::parse_a1(name)
36 }
37
38 fn parse_a1(name: &str) -> Option<CellRef> {
39 let re = Regex::new(r"^(?<letters>[A-Za-z]+)(?<numbers>[0-9]+)$").unwrap();
40 let caps = re.captures(name)?;
41 let letters = &caps["letters"];
42 let numbers = &caps["numbers"];
43
44 let col = letters
45 .to_ascii_uppercase()
46 .bytes()
47 .fold(0usize, |acc, c| acc * 26 + (c - b'A') as usize + 1)
48 - 1;
49
50 let row = numbers.parse::<usize>().ok()?.checked_sub(1)?;
51
52 Some(CellRef::new(row, col))
53 }
54
55 pub fn col_to_letters(col: usize) -> String {
57 let mut result = String::new();
58 let mut n = col + 1;
59 while n > 0 {
60 n -= 1;
61 result.insert(0, (b'A' + (n % 26) as u8) as char);
62 n /= 26;
63 }
64 result
65 }
66}
67
68impl std::str::FromStr for CellRef {
69 type Err = String;
70
71 fn from_str(s: &str) -> Result<Self, Self::Err> {
72 Self::parse_a1(s).ok_or_else(|| format!("Invalid cell reference: {}", s))
73 }
74}
75
76impl fmt::Display for CellRef {
77 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
78 write!(f, "{}{}", CellRef::col_to_letters(self.col), self.row + 1)
79 }
80}