visi-core 0.2.19

Embeddable spreadsheet engine: Excel formula compilation and evaluation, dependency-tracked recalculation, and .xlsx import/export
Documentation
use serde::{Deserialize, Serialize};

/// A random 53-bit identifier for a sheet or column.
///
/// Capped to `2^53 - 1` for JSON compatibility
pub fn generate_unique_id() -> u64 {
    let mut buf = [0u8; 8];
    let val = if getrandom::getrandom(&mut buf).is_err() {
        let now = web_time::SystemTime::now()
            .duration_since(web_time::SystemTime::UNIX_EPOCH)
            .map(|d| d.as_nanos())
            .unwrap_or(0);
        now as u64
    } else {
        u64::from_le_bytes(buf)
    };
    val & 0x001F_FFFF_FFFF_FFFF
}

/// The intrinsic data type of a cell, mirroring calamine worksheet value variants.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub enum CellType {
    /// Empty cell (`calamine::Data::Empty`).
    #[default]
    Empty,
    /// Signed integer (`calamine::Data::Int`).
    Int,
    /// Floating point number (`calamine::Data::Float`).
    Float,
    /// String (`calamine::Data::String`).
    String,
    /// Boolean (`calamine::Data::Bool`).
    Bool,
    /// Date/time serial identified by calamine from workbook formatting.
    DateTime,
    /// ISO 8601 date/time (`calamine::Data::DateTimeIso`, OpenXML `t="d"`).
    DateTimeIso,
    /// ISO 8601 duration (`calamine::Data::DurationIso`).
    DurationIso,
    /// Error cell (`calamine::Data::Error`, OpenXML `t="e"`).
    Error,
}

impl CellType {
    /// Whether this cell type is explicitly a string/text cell.
    pub fn is_string(&self) -> bool {
        matches!(self, CellType::String)
    }

    /// Stable lowercase spelling used by CLI and JSON output.
    pub fn as_str(&self) -> &'static str {
        match self {
            CellType::Empty => "empty",
            CellType::Int => "int",
            CellType::Float => "float",
            CellType::String => "string",
            CellType::Bool => "bool",
            CellType::DateTime => "date-time",
            CellType::DateTimeIso => "date-time-iso",
            CellType::DurationIso => "duration-iso",
            CellType::Error => "error",
        }
    }
}

/// For either a column or row
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum RefType {
    /// Written without a `$`, so it shifts
    /// when the formula is filled or copied
    Relative,
    /// Written with a `$`, so it stays put
    Absolute,
}

impl std::fmt::Display for RefType {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            RefType::Relative => write!(f, ""),
            RefType::Absolute => write!(f, "$"),
        }
    }
}

/// A cell's position, plus whether it was written as absolute
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct CellRef {
    /// Row index, 0-based
    pub row: usize,
    /// Column index, 0-based
    pub col: usize,
    /// Whether the row was written with a `$`.
    pub row_ref_type: RefType,
    /// Whether the column was written with a `$`.
    pub col_ref_type: RefType,
}

impl std::fmt::Display for CellRef {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "CELL({}{}, {}{})",
            self.row_ref_type, self.row, self.col_ref_type, self.col
        )
    }
}

impl CellRef {
    /// A relative reference to `(row, col)`, 0-based.
    pub fn new(row: usize, col: usize) -> CellRef {
        Self {
            row,
            col,
            row_ref_type: RefType::Relative,
            col_ref_type: RefType::Relative,
        }
    }
}

/// Something a formula reads, and therefore
/// an edge in the recalculation graph.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum Dependency {
    /// A cell on the same sheet.
    Local(CellRef),
    /// A whole column on the same sheet, by 0-based position.
    LocalColumn(usize),
    /// A cell on another sheet.
    Remote {
        /// Name of the sheet the cell is on.
        sheet: String,
        /// The cell, on that sheet.
        cell: CellRef,
    },
    /// A whole column on another sheet.
    RemoteColumn {
        /// Name of the sheet the column is on.
        sheet: String,
        /// Column index, 0-based.
        col: usize,
    },
}

/// A caret position: a cell plus an offset within its source text
#[derive(Debug, Clone, Default)]
pub struct TextCellRef {
    /// Row index, 0-based
    pub row: usize,
    /// Column index, 0-based
    pub col: usize,
    /// Offset into the cell's source text, in characters rather than bytes
    pub char_offset: usize,
}

/// A formula that could not be evaluated at all
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EvalError {
    /// The formula could not be parsed, or named something unrecognized
    UnknownFunction(String),
}

impl std::error::Error for EvalError {}

impl std::fmt::Display for EvalError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            EvalError::UnknownFunction(func) => write!(f, "{}", func),
        }
    }
}

/// What the engine's evaluation entry points return on failure.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum EngineError {
    /// A formula could not be evaluated.
    EvalError(EvalError),
}

impl std::error::Error for EngineError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            EngineError::EvalError(err) => Some(err),
        }
    }
}

impl std::fmt::Display for EngineError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            EngineError::EvalError(err) => write!(f, "{}", err),
        }
    }
}

impl From<EvalError> for EngineError {
    fn from(err: EvalError) -> Self {
        EngineError::EvalError(err)
    }
}