use crate::{Error, Result};
use std::{convert::TryFrom, fmt};
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
pub struct Index(u64);
impl Index {
#[inline]
pub const fn reserved(self) -> bool {
self.0 >> 0x3F == 1
}
#[inline]
pub const fn mode(self) -> u8 {
(self.0 >> 0x3B) as u8 & 0b1111
}
#[allow(dead_code)]
#[inline]
pub const fn mode_dep(self) -> u8 {
(self.0 >> 0x38) as u8 & 0b111
}
#[inline]
pub const fn res(self) -> u8 {
let res = (self.0 >> 0x34) as u8 & 0b1111;
debug_assert!(res < 16);
res
}
#[must_use]
#[inline]
pub const fn set_res(self, res: u8) -> Self {
debug_assert!(res < 16);
let mask = 0b1111 << 0x34;
let masked_index = self.0 & !mask;
let shifted_res = ((res & 0b1111) as u64) << 0x34;
Self(masked_index | shifted_res)
}
#[inline]
pub const fn base(self) -> u8 {
let base = (self.0 >> 0x2D) as u8 & 0b111_1111;
debug_assert!(base < 122);
base
}
#[must_use]
#[inline]
pub const fn set_base(self, base: u8) -> Self {
debug_assert!(base < 122);
let cleared_of_base = self.0 & !(0b111_1111 << 0x2D);
let shifted_base = (base as u64 & 0b111_1111) << 0x2D;
Self(cleared_of_base | shifted_base)
}
#[inline]
pub const fn digit(self, res: u8) -> Option<u8> {
debug_assert!(res < 16);
debug_assert!(res > 0);
if res == 0 || res > 15 {
None
} else {
Some(((self.0 >> ((15 - res) * 3)) as u8) & 0b111)
}
}
#[must_use]
#[inline]
pub const fn set_digit(self, res: u8, digit: u8) -> Self {
debug_assert!(digit < 8);
debug_assert!(res > 0);
debug_assert!(res < 16);
let cleared_of_digit = self.0 & !(0b111 << ((15 - res) * 3));
let shifted_digit = (digit as u64) << ((15 - res) * 3);
Self(cleared_of_digit | shifted_digit)
}
}
#[derive(Clone, Copy, Eq, Hash, PartialEq)]
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(transparent)
)]
pub struct Cell(pub(crate) u64);
impl Cell {
#[inline]
pub const fn from_raw(raw: u64) -> Result<Self> {
let idx = Index(raw);
if
!idx.reserved() &&
idx.mode() == 1 &&
idx.base() < 122
{
Ok(Cell(idx.0))
} else {
Err(Error::Index(raw))
}
}
#[inline]
pub const fn into_raw(self) -> u64 {
self.0
}
#[inline]
pub const fn to_parent(&self, res: u8) -> Option<Self> {
match self.res() {
v if v < res => None,
v if v == res => Some(*self),
_ => {
let idx = Index(self.0);
let idx = idx.set_res(res);
let lower_bits = u64::MAX >> (64 - (15 - res) * 3);
let raw = idx.0 | lower_bits;
Some(Cell(raw))
}
}
}
#[inline]
pub(crate) const fn base(&self) -> u8 {
let base = Index(self.0).base();
debug_assert!(base < 122, "valid base indices are [0,122]");
base
}
#[inline]
pub const fn res(&self) -> u8 {
Index(self.0).res()
}
#[inline]
pub fn is_related_to(&self, other: &Self) -> bool {
let common_res = std::cmp::min(self.res(), other.res());
let promoted_self = self
.to_parent(common_res)
.expect("we already checked to the min common resolution");
let promoted_other = other
.to_parent(common_res)
.expect("we already checked to the min common resolution");
promoted_self == promoted_other
}
}
impl TryFrom<u64> for Cell {
type Error = Error;
fn try_from(raw: u64) -> Result<Cell> {
Cell::from_raw(raw)
}
}
impl TryFrom<i64> for Cell {
type Error = Error;
fn try_from(raw: i64) -> Result<Cell> {
Cell::from_raw(raw as u64)
}
}
pub(crate) struct CellStack(Option<Cell>);
impl CellStack {
pub fn new() -> Self {
Self(None)
}
pub fn cell(&self) -> Option<&Cell> {
self.0.as_ref()
}
pub(crate) fn push(&mut self, digit: u8) {
match self.0 {
None => {
let idx = Index(0x8001fffffffffff).set_base(digit);
self.0 = Some(Cell(idx.0))
}
Some(cell) => {
let res = cell.res();
let idx = Index(cell.0).set_res(res + 1).set_digit(res + 1, digit);
self.0 = Some(Cell(idx.0))
}
}
}
pub fn pop(&mut self) -> Option<u8> {
if let Some(cell) = self.0 {
let res = cell.res();
if res == 0 {
let ret = Some(cell.base());
self.0 = None;
ret
} else {
let ret = Index(cell.0).digit(res);
self.0 = cell.to_parent(res - 1);
ret
}
} else {
None
}
}
pub fn swap(&mut self, digit: u8) -> Option<u8> {
let ret;
let inner;
if let Some(cell) = self.0 {
let res = cell.res();
if res == 0 {
ret = Some(Index(cell.0).base());
inner = Some(Cell(Index(cell.0).set_base(digit).0));
} else {
ret = Index(cell.0).digit(res);
inner = Some(Cell(Index(cell.0).set_digit(res, digit).0));
}
} else {
return None;
}
self.0 = inner;
ret
}
}
impl From<Cell> for CellStack {
fn from(cell: Cell) -> CellStack {
CellStack(Some(cell))
}
}
impl fmt::Debug for Cell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
write!(f, "{:0x}", self.0)
}
}
impl fmt::Display for Cell {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> std::result::Result<(), fmt::Error> {
write!(f, "{:x}", self.0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_index_bitfields() {
let idx = Index(0x85283473fffffff);
assert!(!idx.reserved());
assert_eq!(idx.mode(), 1);
assert_eq!(idx.mode_dep(), 0);
assert_eq!(idx.res(), 5);
assert_eq!(idx.base(), 20);
assert_eq!(idx.digit(1), Some(0));
assert_eq!(idx.digit(2), Some(6));
assert_eq!(idx.digit(3), Some(4));
assert_eq!(idx.digit(4), Some(3));
assert_eq!(idx.digit(5), Some(4));
assert_eq!(idx.digit(6), Some(7));
assert_eq!(idx.digit(7), Some(7));
assert_eq!(idx.digit(8), Some(7));
assert_eq!(idx.digit(9), Some(7));
assert_eq!(idx.digit(10), Some(7));
assert_eq!(idx.digit(11), Some(7));
assert_eq!(idx.digit(12), Some(7));
assert_eq!(idx.digit(13), Some(7));
assert_eq!(idx.digit(14), Some(7));
assert_eq!(idx.digit(15), Some(7));
}
#[test]
fn test_cell_to_parent() {
let cell = Cell::from_raw(0x85283473fffffff).unwrap();
let parent = cell.to_parent(cell.res()).unwrap();
assert_eq!(cell, parent);
let parent = cell.to_parent(4).unwrap();
let parent_idx = Index(parent.0);
assert_eq!(parent.res(), 4);
assert_eq!(parent_idx.digit(5), Some(7));
assert_eq!(parent_idx.digit(4), Some(3));
let parent = cell.to_parent(0).unwrap();
let parent_idx = Index(parent.0);
assert_eq!(parent_idx.digit(4), Some(7));
assert_eq!(parent_idx.digit(3), Some(7));
assert_eq!(parent_idx.digit(2), Some(7));
assert_eq!(parent_idx.digit(1), Some(7));
assert_eq!(parent_idx.base(), 20);
}
}