1use std::fmt;
4
5use thiserror::Error;
6
7use crate::eqv::{EqvRelation, SymbolicEqv};
8
9#[derive(Copy, Clone, Eq, PartialEq)]
11enum Offset {
12 Rel(usize),
13 Abs(usize),
14}
15
16#[derive(Clone, Copy, Eq, PartialOrd, Ord)]
18pub struct CellRef {
19 col: usize,
20 base: Option<usize>,
21 offset: usize,
22}
23
24impl CellRef {
25 pub fn absolute(col: usize, row: usize) -> Self {
27 Self {
28 col,
29 base: None,
30 offset: row,
31 }
32 }
33
34 pub fn relative(col: usize, base: usize, offset: usize) -> Self {
37 log::debug!("Creating relative reference (col: {col}, base: {base}, offset: {offset})");
38 Self {
39 col,
40 base: Some(base),
41 offset,
42 }
43 }
44
45 pub fn row(&self) -> usize {
47 self.base.unwrap_or_default() + self.offset
48 }
49
50 fn offset(&self) -> Offset {
52 match self.base {
53 Some(_) => Offset::Rel(self.offset),
54 None => Offset::Abs(self.offset),
55 }
56 }
57
58 pub fn col(&self) -> usize {
60 self.col
61 }
62
63 pub fn is_absolute(&self) -> bool {
65 self.base.is_none()
66 }
67
68 pub fn relativize(&self, base: usize) -> Self {
76 self.try_relativize(base)
77 .map_err(|e| panic!("{e}"))
78 .unwrap()
79 }
80
81 pub fn try_relativize(&self, base: usize) -> Result<Self, CellError> {
85 match self.offset() {
86 Offset::Abs(offset) => {
87 if base > offset {
88 return Err(CellError::BaseAfterOffset { base, offset });
89 }
90 let offset = offset - base;
91 Ok(Self::relative(self.col, base, offset))
92 }
93 Offset::Rel(_) => Err(CellError::AttemptedRelativizeRelativeCell),
94 }
95 }
96
97 pub fn absolutize(&self) -> Self {
101 Self::absolute(self.col, self.row())
102 }
103}
104
105impl fmt::Debug for CellRef {
106 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107 match self.base {
108 Some(base) => write!(f, "[{}, {base}(+{})]", self.col, self.offset),
109 None => write!(f, "[{}, {}]", self.col, self.offset),
110 }
111 }
112}
113
114impl fmt::Display for CellRef {
115 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116 match self.base {
117 Some(base) => write!(f, "[{},{base}(+{})]", self.col, self.offset),
118 None => write!(f, "[{},{}]", self.col, self.offset),
119 }
120 }
121}
122
123impl PartialEq for CellRef {
124 fn eq(&self, other: &Self) -> bool {
125 self.col() == other.col() && self.row() == other.row()
126 }
127}
128
129impl std::hash::Hash for CellRef {
130 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
131 self.col().hash(state);
132 self.row().hash(state);
133 }
134}
135
136impl EqvRelation<CellRef> for SymbolicEqv {
137 fn equivalent(lhs: &CellRef, rhs: &CellRef) -> bool {
140 lhs.col() == rhs.col()
141 && (
142 lhs.row() == rhs.row() ||
144 lhs.offset() == rhs.offset()
146 )
147 }
148}
149
150#[derive(Error, Copy, Clone, Debug)]
152pub enum CellError {
153 #[error("failed to relativize cell reference: offset {offset} points to a row before {base}")]
156 BaseAfterOffset {
157 base: usize,
159 offset: usize,
161 },
162 #[error("cannot relativize a relative cell")]
165 AttemptedRelativizeRelativeCell,
166}
167
168#[cfg(test)]
169mod tests {
170 use std::hash::{DefaultHasher, Hash as _, Hasher as _};
171
172 use log::LevelFilter;
173 use quickcheck_macros::quickcheck;
174 use simplelog::{Config, TestLogger};
175
176 use super::*;
177
178 macro_rules! checked {
179 ($name:ident, $e:expr) => {
180 let Some($name) = $e else {
181 return true;
182 };
183 };
184 ($e:expr) => {
185 if let None = $e {
186 return true;
187 }
188 };
189 }
190
191 #[quickcheck]
194 fn same_absolute_and_relative_equal(col: usize, base: usize, offset: usize) -> bool {
195 let _ = TestLogger::init(LevelFilter::Debug, Config::default());
196 checked!(base_offset, base.checked_add(offset));
198 CellRef::absolute(col, base_offset) == CellRef::relative(col, base, offset)
199 }
200
201 #[quickcheck]
204 fn diff_col_absolute_and_relative_not_equal(col: usize, base: usize, offset: usize) -> bool {
205 let _ = TestLogger::init(LevelFilter::Debug, Config::default());
206 checked!(base_offset, base.checked_add(offset));
208 checked!(col_1, col.checked_add(1));
209 CellRef::absolute(col, base_offset) != CellRef::relative(col_1, base, offset)
210 }
211
212 #[quickcheck]
215 fn diff_row_absolute_and_relative_not_equal_1(col: usize, base: usize, offset: usize) -> bool {
216 let _ = TestLogger::init(LevelFilter::Debug, Config::default());
217 checked!(base_offset, base.checked_add(offset));
219 checked!(base_offset.checked_add(1));
220 CellRef::absolute(col, base_offset) != CellRef::relative(col, base + 1, offset)
221 }
222
223 #[quickcheck]
226 fn diff_row_absolute_and_relative_not_equal_2(col: usize, base: usize, offset: usize) -> bool {
227 let _ = TestLogger::init(LevelFilter::Debug, Config::default());
228 checked!(base_offset, base.checked_add(offset));
230 checked!(base_offset.checked_add(1));
231 CellRef::absolute(col, base_offset) != CellRef::relative(col, base, offset + 1)
232 }
233
234 fn hash(cell: CellRef) -> u64 {
235 let mut h = DefaultHasher::new();
236 cell.hash(&mut h);
237 h.finish()
238 }
239
240 #[quickcheck]
243 fn same_absolute_and_relative_equal_hash(col: usize, base: usize, offset: usize) -> bool {
244 let _ = TestLogger::init(LevelFilter::Debug, Config::default());
245 checked!(base_offset, base.checked_add(offset));
247 hash(CellRef::absolute(col, base_offset)) == hash(CellRef::relative(col, base, offset))
248 }
249
250 #[quickcheck]
253 fn diff_col_absolute_and_relative_not_equal_hash(
254 col: usize,
255 base: usize,
256 offset: usize,
257 ) -> bool {
258 let _ = TestLogger::init(LevelFilter::Debug, Config::default());
259 checked!(base_offset, base.checked_add(offset));
261 checked!(col_1, col.checked_add(1));
262 hash(CellRef::absolute(col, base_offset)) != hash(CellRef::relative(col_1, base, offset))
263 }
264
265 #[quickcheck]
268 fn diff_row_absolute_and_relative_not_equal_1_hash(
269 col: usize,
270 base: usize,
271 offset: usize,
272 ) -> bool {
273 let _ = TestLogger::init(LevelFilter::Debug, Config::default());
274 checked!(base_offset, base.checked_add(offset));
276 checked!(base_1, base.checked_add(1));
277 checked!(base_1.checked_add(offset));
278 hash(CellRef::absolute(col, base_offset)) != hash(CellRef::relative(col, base_1, offset))
279 }
280
281 #[quickcheck]
284 fn diff_row_absolute_and_relative_not_equal_2_hash(
285 col: usize,
286 base: usize,
287 offset: usize,
288 ) -> bool {
289 let _ = TestLogger::init(LevelFilter::Debug, Config::default());
290 checked!(base_offset, base.checked_add(offset));
292 checked!(base_offset.checked_add(1));
293 checked!(offset_1, offset.checked_add(1));
294 hash(CellRef::absolute(col, base_offset)) != hash(CellRef::relative(col, base, offset_1))
295 }
296
297 #[quickcheck]
300 fn same_relative_sym_eqv(col: usize, base: usize, offset: usize) -> bool {
301 let _ = TestLogger::init(LevelFilter::Debug, Config::default());
302 checked!(base_offset, base.checked_add(offset));
304 checked!(base_1, base.checked_add(1));
305 checked!(base_offset.checked_add(1));
306 SymbolicEqv::equivalent(
307 &CellRef::relative(col, base_1, offset),
308 &CellRef::relative(col, base, offset),
309 )
310 }
311}