gridline_engine/engine/
cell_ref.rs1use regex::Regex;
2use serde::{Deserialize, Serialize};
3use std::fmt;
4
5#[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 #[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 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}