use std::fmt;
use thiserror::Error;
use crate::eqv::{EqvRelation, SymbolicEqv};
#[derive(Copy, Clone, Eq, PartialEq)]
enum Offset {
Rel(usize),
Abs(usize),
}
#[derive(Clone, Copy, Eq, PartialOrd, Ord)]
pub struct CellRef {
col: usize,
base: Option<usize>,
offset: usize,
}
impl CellRef {
pub fn absolute(col: usize, row: usize) -> Self {
Self {
col,
base: None,
offset: row,
}
}
pub fn relative(col: usize, base: usize, offset: usize) -> Self {
log::debug!("Creating relative reference (col: {col}, base: {base}, offset: {offset})");
Self {
col,
base: Some(base),
offset,
}
}
pub fn row(&self) -> usize {
self.base.unwrap_or_default() + self.offset
}
fn offset(&self) -> Offset {
match self.base {
Some(_) => Offset::Rel(self.offset),
None => Offset::Abs(self.offset),
}
}
pub fn col(&self) -> usize {
self.col
}
pub fn is_absolute(&self) -> bool {
self.base.is_none()
}
pub fn relativize(&self, base: usize) -> Self {
self.try_relativize(base)
.map_err(|e| panic!("{e}"))
.unwrap()
}
pub fn try_relativize(&self, base: usize) -> Result<Self, CellError> {
match self.offset() {
Offset::Abs(offset) => {
if base > offset {
return Err(CellError::BaseAfterOffset { base, offset });
}
let offset = offset - base;
Ok(Self::relative(self.col, base, offset))
}
Offset::Rel(_) => Err(CellError::AttemptedRelativizeRelativeCell),
}
}
pub fn absolutize(&self) -> Self {
Self::absolute(self.col, self.row())
}
}
impl fmt::Debug for CellRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.base {
Some(base) => write!(f, "[{}, {base}(+{})]", self.col, self.offset),
None => write!(f, "[{}, {}]", self.col, self.offset),
}
}
}
impl fmt::Display for CellRef {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self.base {
Some(base) => write!(f, "[{},{base}(+{})]", self.col, self.offset),
None => write!(f, "[{},{}]", self.col, self.offset),
}
}
}
impl PartialEq for CellRef {
fn eq(&self, other: &Self) -> bool {
self.col() == other.col() && self.row() == other.row()
}
}
impl std::hash::Hash for CellRef {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.col().hash(state);
self.row().hash(state);
}
}
impl EqvRelation<CellRef> for SymbolicEqv {
fn equivalent(lhs: &CellRef, rhs: &CellRef) -> bool {
lhs.col() == rhs.col()
&& (
lhs.row() == rhs.row() ||
lhs.offset() == rhs.offset()
)
}
}
#[derive(Error, Copy, Clone, Debug)]
pub enum CellError {
#[error("failed to relativize cell reference: offset {offset} points to a row before {base}")]
BaseAfterOffset {
base: usize,
offset: usize,
},
#[error("cannot relativize a relative cell")]
AttemptedRelativizeRelativeCell,
}
#[cfg(test)]
mod tests {
use std::hash::{DefaultHasher, Hash as _, Hasher as _};
use log::LevelFilter;
use quickcheck_macros::quickcheck;
use simplelog::{Config, TestLogger};
use super::*;
macro_rules! checked {
($name:ident, $e:expr) => {
let Some($name) = $e else {
return true;
};
};
($e:expr) => {
if let None = $e {
return true;
}
};
}
#[quickcheck]
fn same_absolute_and_relative_equal(col: usize, base: usize, offset: usize) -> bool {
let _ = TestLogger::init(LevelFilter::Debug, Config::default());
checked!(base_offset, base.checked_add(offset));
CellRef::absolute(col, base_offset) == CellRef::relative(col, base, offset)
}
#[quickcheck]
fn diff_col_absolute_and_relative_not_equal(col: usize, base: usize, offset: usize) -> bool {
let _ = TestLogger::init(LevelFilter::Debug, Config::default());
checked!(base_offset, base.checked_add(offset));
checked!(col_1, col.checked_add(1));
CellRef::absolute(col, base_offset) != CellRef::relative(col_1, base, offset)
}
#[quickcheck]
fn diff_row_absolute_and_relative_not_equal_1(col: usize, base: usize, offset: usize) -> bool {
let _ = TestLogger::init(LevelFilter::Debug, Config::default());
checked!(base_offset, base.checked_add(offset));
checked!(base_offset.checked_add(1));
CellRef::absolute(col, base_offset) != CellRef::relative(col, base + 1, offset)
}
#[quickcheck]
fn diff_row_absolute_and_relative_not_equal_2(col: usize, base: usize, offset: usize) -> bool {
let _ = TestLogger::init(LevelFilter::Debug, Config::default());
checked!(base_offset, base.checked_add(offset));
checked!(base_offset.checked_add(1));
CellRef::absolute(col, base_offset) != CellRef::relative(col, base, offset + 1)
}
fn hash(cell: CellRef) -> u64 {
let mut h = DefaultHasher::new();
cell.hash(&mut h);
h.finish()
}
#[quickcheck]
fn same_absolute_and_relative_equal_hash(col: usize, base: usize, offset: usize) -> bool {
let _ = TestLogger::init(LevelFilter::Debug, Config::default());
checked!(base_offset, base.checked_add(offset));
hash(CellRef::absolute(col, base_offset)) == hash(CellRef::relative(col, base, offset))
}
#[quickcheck]
fn diff_col_absolute_and_relative_not_equal_hash(
col: usize,
base: usize,
offset: usize,
) -> bool {
let _ = TestLogger::init(LevelFilter::Debug, Config::default());
checked!(base_offset, base.checked_add(offset));
checked!(col_1, col.checked_add(1));
hash(CellRef::absolute(col, base_offset)) != hash(CellRef::relative(col_1, base, offset))
}
#[quickcheck]
fn diff_row_absolute_and_relative_not_equal_1_hash(
col: usize,
base: usize,
offset: usize,
) -> bool {
let _ = TestLogger::init(LevelFilter::Debug, Config::default());
checked!(base_offset, base.checked_add(offset));
checked!(base_1, base.checked_add(1));
checked!(base_1.checked_add(offset));
hash(CellRef::absolute(col, base_offset)) != hash(CellRef::relative(col, base_1, offset))
}
#[quickcheck]
fn diff_row_absolute_and_relative_not_equal_2_hash(
col: usize,
base: usize,
offset: usize,
) -> bool {
let _ = TestLogger::init(LevelFilter::Debug, Config::default());
checked!(base_offset, base.checked_add(offset));
checked!(base_offset.checked_add(1));
checked!(offset_1, offset.checked_add(1));
hash(CellRef::absolute(col, base_offset)) != hash(CellRef::relative(col, base, offset_1))
}
#[quickcheck]
fn same_relative_sym_eqv(col: usize, base: usize, offset: usize) -> bool {
let _ = TestLogger::init(LevelFilter::Debug, Config::default());
checked!(base_offset, base.checked_add(offset));
checked!(base_1, base.checked_add(1));
checked!(base_offset.checked_add(1));
SymbolicEqv::equivalent(
&CellRef::relative(col, base_1, offset),
&CellRef::relative(col, base, offset),
)
}
}