1use core::fmt;
24
25use crate::engine::sheet_registry::SheetRegistry; use formualizer_common::{ExcelError, ExcelErrorKind};
27use formualizer_parse::parser::ReferenceType;
28
29#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
38pub struct Coord {
39 pub row: u32,
40 pub col: u32,
41 flags: u8,
42}
43
44impl Coord {
45 #[inline]
47 pub const fn new(row: u32, col: u32, row_abs: bool, col_abs: bool) -> Self {
48 let flags = (row_abs as u8) | ((col_abs as u8) << 1);
49 Self { row, col, flags }
50 }
51
52 #[inline]
54 pub const fn row_abs(self) -> bool {
55 self.flags & 0b01 != 0
56 }
57 #[inline]
58 pub const fn col_abs(self) -> bool {
59 self.flags & 0b10 != 0
60 }
61
62 #[inline]
64 pub const fn with_row_abs(mut self, abs: bool) -> Self {
65 if abs {
66 self.flags |= 0b01
67 } else {
68 self.flags &= !0b01;
69 }
70 self
71 }
72 #[inline]
74 pub const fn with_col_abs(mut self, abs: bool) -> Self {
75 if abs {
76 self.flags |= 0b10
77 } else {
78 self.flags &= !0b10;
79 }
80 self
81 }
82
83 #[inline]
85 pub const fn offset(self, drow: i32, dcol: i32) -> Self {
86 Self {
87 row: ((self.row as i32) + drow) as u32,
88 col: ((self.col as i32) + dcol) as u32,
89 flags: self.flags,
90 }
91 }
92
93 #[inline]
96 pub fn rebase(self, origin: Coord, target: Coord) -> Self {
97 let drow = target.row as i32 - origin.row as i32;
98 let dcol = target.col as i32 - origin.col as i32;
99 let new_row = if self.row_abs() {
100 self.row
101 } else {
102 ((self.row as i32) + drow) as u32
103 };
104 let new_col = if self.col_abs() {
105 self.col
106 } else {
107 ((self.col as i32) + dcol) as u32
108 };
109 Self {
110 row: new_row,
111 col: new_col,
112 flags: self.flags,
113 }
114 }
115
116 pub fn col_to_letters(mut col: u32) -> String {
120 let mut buf = String::new();
122 loop {
123 let rem = (col % 26) as u8;
124 buf.push(char::from(b'A' + rem));
125 col /= 26;
126 if col == 0 {
127 break;
128 }
129 col -= 1; }
131 buf.chars().rev().collect()
132 }
133
134 pub fn letters_to_col(s: &str) -> Option<u32> {
136 let mut col: u32 = 0;
137 for (i, ch) in s.bytes().enumerate() {
138 if !ch.is_ascii_uppercase() {
139 return None;
140 }
141 let val = (ch - b'A') as u32;
142 col = col * 26 + val;
143 if i != s.len() - 1 {
144 col += 1; }
146 }
147 Some(col)
148 }
149}
150
151type SheetBounds = (Option<String>, (u32, u32, u32, u32));
152
153pub fn combine_references(
157 a: &ReferenceType,
158 b: &ReferenceType,
159) -> Result<ReferenceType, ExcelError> {
160 fn to_bounds(r: &ReferenceType) -> Option<SheetBounds> {
162 match r {
163 ReferenceType::Cell { sheet, row, col } => {
164 Some((sheet.clone(), (*row, *col, *row, *col)))
165 }
166 ReferenceType::Range {
167 sheet,
168 start_row,
169 start_col,
170 end_row,
171 end_col,
172 } => {
173 let (sr, sc, er, ec) = match (start_row, start_col, end_row, end_col) {
174 (Some(sr), Some(sc), Some(er), Some(ec)) => (*sr, *sc, *er, *ec),
175 _ => return None,
176 };
177 Some((sheet.clone(), (sr, sc, er, ec)))
178 }
179 _ => None,
180 }
181 }
182
183 let (sheet_a, (a_sr, a_sc, a_er, a_ec)) = to_bounds(a).ok_or_else(|| {
184 ExcelError::new(ExcelErrorKind::Ref).with_message("Unsupported reference for ':'")
185 })?;
186 let (sheet_b, (b_sr, b_sc, b_er, b_ec)) = to_bounds(b).ok_or_else(|| {
187 ExcelError::new(ExcelErrorKind::Ref).with_message("Unsupported reference for ':'")
188 })?;
189
190 if sheet_a != sheet_b {
192 return Err(ExcelError::new(ExcelErrorKind::Ref)
193 .with_message("Cannot combine references across sheets"));
194 }
195
196 let sr = a_sr.min(b_sr);
197 let sc = a_sc.min(b_sc);
198 let er = a_er.max(b_er);
199 let ec = a_ec.max(b_ec);
200
201 Ok(ReferenceType::Range {
202 sheet: sheet_a,
203 start_row: Some(sr),
204 start_col: Some(sc),
205 end_row: Some(er),
206 end_col: Some(ec),
207 })
208}
209
210impl fmt::Display for Coord {
211 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
212 if self.col_abs() {
213 write!(f, "$")?;
214 }
215 write!(f, "{}", Self::col_to_letters(self.col))?;
216 if self.row_abs() {
217 write!(f, "$")?;
218 }
219 write!(f, "{}", self.row + 1)
221 }
222}
223
224pub type SheetId = u16; #[derive(Clone, Copy, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
235pub struct CellRef {
236 pub sheet_id: SheetId, pub coord: Coord,
238}
239
240impl CellRef {
241 #[inline]
242 pub const fn new(sheet_id: SheetId, coord: Coord) -> Self {
243 Self { sheet_id, coord }
244 }
245
246 #[inline]
247 pub fn new_absolute(sheet_id: SheetId, row: u32, col: u32) -> Self {
248 Self {
249 sheet_id,
250 coord: Coord::new(row, col, true, true),
251 }
252 }
253
254 #[inline]
256 pub fn rebase(self, origin: Coord, target: Coord) -> Self {
257 Self {
258 sheet_id: self.sheet_id,
259 coord: self.coord.rebase(origin, target),
260 }
261 }
262
263 #[inline]
264 pub fn sheet_name<'a>(&self, sheet_reg: &'a SheetRegistry) -> &'a str {
265 sheet_reg.name(self.sheet_id)
266 }
267}
268
269impl fmt::Display for CellRef {
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 if self.sheet_id != 0 {
272 write!(f, "Sheet{}!", self.sheet_id)?; }
274 write!(f, "{}", self.coord)
275 }
276}
277
278#[derive(Clone, Copy, Debug, Eq, PartialEq, Hash)]
283pub struct RangeRef {
284 pub start: CellRef,
285 pub end: CellRef, }
287
288impl RangeRef {
289 #[inline]
290 pub const fn new(start: CellRef, end: CellRef) -> Self {
291 Self { start, end }
292 }
293}
294
295impl fmt::Display for RangeRef {
296 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
297 if self.start.sheet_id == self.end.sheet_id {
298 write!(f, "{}:{}", self.start, self.end.coord)
300 } else {
301 write!(f, "{}:{}", self.start, self.end)
303 }
304 }
305}
306
307#[cfg(test)]
312mod tests {
313 use super::*;
314
315 #[test]
316 fn test_display_coord() {
317 let c = Coord::new(0, 0, false, false);
318 assert_eq!(c.to_string(), "A1");
319 let c = Coord::new(7, 27, true, true); assert_eq!(c.to_string(), "$AB$8");
321 }
322
323 #[test]
324 fn test_rebase() {
325 let origin = Coord::new(0, 0, false, false);
326 let target = Coord::new(1, 1, false, false);
327 let formula_coord = Coord::new(2, 0, false, true); let rebased = formula_coord.rebase(origin, target);
329 assert_eq!(rebased, Coord::new(3, 0, false, true));
331 }
332
333 #[test]
334 fn test_range_display() {
335 let a1 = CellRef::new(0, Coord::new(0, 0, false, false));
336 let b2 = CellRef::new(0, Coord::new(1, 1, false, false));
337 let r = RangeRef::new(a1, b2);
338 assert_eq!(r.to_string(), "A1:B2");
339 }
340}