use std::error::Error;
use std::fmt;
use crate::{CellRange, FormulaText, SheetId, WorkbookSnapshot};
use super::{CalculationOptions, CancellationToken};
mod analyzer;
#[cfg(test)]
mod tests;
const MESSAGE_ZERO_LIMIT: &str = "defined-name analysis limit must be greater than zero";
const MESSAGE_UNKNOWN_SHEET: &str = "current sheet does not exist in the workbook";
const MESSAGE_RESOURCE_LIMIT: &str = "defined-name analysis exceeded a configured resource limit";
const MESSAGE_CANCELLED: &str = "defined-name analysis was cancelled";
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct DefinedNameSheetSpan {
start: SheetId,
end: SheetId,
}
impl DefinedNameSheetSpan {
pub(super) const fn new(start: SheetId, end: SheetId) -> Self {
Self { start, end }
}
pub const fn start(self) -> SheetId {
self.start
}
pub const fn end(self) -> SheetId {
self.end
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum DefinedNameReferenceArea {
Rectangular {
sheet_id: SheetId,
range: CellRange,
},
ThreeDimensional {
sheet_span: DefinedNameSheetSpan,
range: CellRange,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DefinedNameDynamicKind {
Offset,
Indirect,
Spill,
Mixed,
}
impl DefinedNameDynamicKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::Offset => "offset",
Self::Indirect => "indirect",
Self::Spill => "spill",
Self::Mixed => "mixed",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DefinedNameExternalTargetKind {
Reference,
DefinedName,
StructuredReference,
}
impl DefinedNameExternalTargetKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::Reference => "reference",
Self::DefinedName => "defined_name",
Self::StructuredReference => "structured_reference",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DefinedNameExternalReference {
locator: Option<Box<str>>,
workbook: Box<str>,
sheet: Option<Box<str>>,
sheet_end: Option<Box<str>>,
target: DefinedNameExternalTargetKind,
target_text: Box<str>,
}
impl DefinedNameExternalReference {
pub(super) fn new(
locator: Option<Box<str>>,
workbook: Box<str>,
sheet: Option<Box<str>>,
sheet_end: Option<Box<str>>,
target: DefinedNameExternalTargetKind,
target_text: Box<str>,
) -> Self {
Self {
locator,
workbook,
sheet,
sheet_end,
target,
target_text,
}
}
pub fn locator(&self) -> Option<&str> {
self.locator.as_deref()
}
pub fn workbook(&self) -> &str {
&self.workbook
}
pub fn sheet(&self) -> Option<&str> {
self.sheet.as_deref()
}
pub fn sheet_end(&self) -> Option<&str> {
self.sheet_end.as_deref()
}
pub const fn target(&self) -> DefinedNameExternalTargetKind {
self.target
}
pub fn target_text(&self) -> &str {
&self.target_text
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DefinedNameInvalidReason {
ParseError,
CircularReference,
UnresolvedName,
InvalidReference,
}
impl DefinedNameInvalidReason {
pub const fn as_str(self) -> &'static str {
match self {
Self::ParseError => "parse_error",
Self::CircularReference => "circular_reference",
Self::UnresolvedName => "unresolved_name",
Self::InvalidReference => "invalid_reference",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DefinedNameUnsupportedReason {
NonReferenceExpression,
ContextDependent,
UnsupportedExpression,
}
impl DefinedNameUnsupportedReason {
pub const fn as_str(self) -> &'static str {
match self {
Self::NonReferenceExpression => "non_reference_expression",
Self::ContextDependent => "context_dependent",
Self::UnsupportedExpression => "unsupported_expression",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum DefinedNameAnalysis {
Rectangular {
sheet_id: SheetId,
range: CellRange,
},
ThreeDimensional {
sheet_span: DefinedNameSheetSpan,
range: CellRange,
},
NonRectangular {
areas: Vec<DefinedNameReferenceArea>,
},
EmptyReference,
DynamicFormula {
kind: DefinedNameDynamicKind,
formula: FormulaText,
},
Constant {
formula: FormulaText,
},
ExternalReference {
detail: DefinedNameExternalReference,
},
Invalid {
reason: DefinedNameInvalidReason,
detail: Option<Box<str>>,
},
Unsupported {
reason: DefinedNameUnsupportedReason,
detail: Option<Box<str>>,
},
NotFound,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DefinedNameAnalysisLimitKind {
FormulaTokens,
FormulaSourceBytes,
FormulaAstNodes,
FormulaNestingDepth,
NameChainDepth,
ScanNodes,
ReferenceAreas,
FunctionIterations,
}
impl DefinedNameAnalysisLimitKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::FormulaTokens => "formula_tokens",
Self::FormulaSourceBytes => "formula_source_bytes",
Self::FormulaAstNodes => "formula_ast_nodes",
Self::FormulaNestingDepth => "formula_nesting_depth",
Self::NameChainDepth => "name_chain_depth",
Self::ScanNodes => "scan_nodes",
Self::ReferenceAreas => "reference_areas",
Self::FunctionIterations => "function_iterations",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[non_exhaustive]
pub enum DefinedNameAnalysisErrorKind {
UnknownCurrentSheet,
ResourceLimit,
Cancelled,
}
impl DefinedNameAnalysisErrorKind {
pub const fn as_str(self) -> &'static str {
match self {
Self::UnknownCurrentSheet => "defined_name.unknown_current_sheet",
Self::ResourceLimit => "defined_name.resource_limit",
Self::Cancelled => "defined_name.cancelled",
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct DefinedNameAnalysisError {
kind: DefinedNameAnalysisErrorKind,
limit: Option<DefinedNameAnalysisLimitKind>,
detail: Option<Box<str>>,
}
impl DefinedNameAnalysisError {
pub(super) fn unknown_sheet(sheet_id: SheetId) -> Self {
Self {
kind: DefinedNameAnalysisErrorKind::UnknownCurrentSheet,
limit: None,
detail: Some(sheet_id.get().to_string().into_boxed_str()),
}
}
pub(super) fn resource(limit: DefinedNameAnalysisLimitKind) -> Self {
Self {
kind: DefinedNameAnalysisErrorKind::ResourceLimit,
limit: Some(limit),
detail: Some(limit.as_str().into()),
}
}
pub(super) fn cancelled() -> Self {
Self {
kind: DefinedNameAnalysisErrorKind::Cancelled,
limit: None,
detail: None,
}
}
pub const fn kind(&self) -> DefinedNameAnalysisErrorKind {
self.kind
}
pub const fn limit(&self) -> Option<DefinedNameAnalysisLimitKind> {
self.limit
}
pub fn detail(&self) -> Option<&str> {
self.detail.as_deref()
}
pub const fn message(&self) -> &'static str {
match self.kind {
DefinedNameAnalysisErrorKind::UnknownCurrentSheet => MESSAGE_UNKNOWN_SHEET,
DefinedNameAnalysisErrorKind::ResourceLimit => MESSAGE_RESOURCE_LIMIT,
DefinedNameAnalysisErrorKind::Cancelled => MESSAGE_CANCELLED,
}
}
}
impl fmt::Display for DefinedNameAnalysisError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(self.message())
}
}
impl Error for DefinedNameAnalysisError {}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct DefinedNameAnalysisOptions {
calculation: CalculationOptions,
max_name_chain_depth: u64,
max_scan_nodes: u64,
}
impl DefinedNameAnalysisOptions {
pub const fn new(calculation: CalculationOptions) -> Self {
Self {
calculation,
max_name_chain_depth: 256,
max_scan_nodes: 65_536,
}
}
pub fn with_max_name_chain_depth(
mut self,
value: u64,
) -> Result<Self, DefinedNameAnalysisOptionsError> {
if value == 0 {
return Err(DefinedNameAnalysisOptionsError);
}
self.max_name_chain_depth = value;
Ok(self)
}
pub fn with_max_scan_nodes(
mut self,
value: u64,
) -> Result<Self, DefinedNameAnalysisOptionsError> {
if value == 0 {
return Err(DefinedNameAnalysisOptionsError);
}
self.max_scan_nodes = value;
Ok(self)
}
pub const fn calculation(self) -> CalculationOptions {
self.calculation
}
pub const fn max_name_chain_depth(self) -> u64 {
self.max_name_chain_depth
}
pub const fn max_scan_nodes(self) -> u64 {
self.max_scan_nodes
}
}
impl Default for DefinedNameAnalysisOptions {
fn default() -> Self {
Self::new(CalculationOptions::default())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DefinedNameAnalysisOptionsError;
impl fmt::Display for DefinedNameAnalysisOptionsError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str(MESSAGE_ZERO_LIMIT)
}
}
impl Error for DefinedNameAnalysisOptionsError {}
pub fn analyze_defined_name(
workbook: &WorkbookSnapshot,
name: &str,
current_sheet: Option<SheetId>,
) -> Result<DefinedNameAnalysis, DefinedNameAnalysisError> {
analyzer::analyze(
workbook,
name,
current_sheet,
DefinedNameAnalysisOptions::default(),
&|| false,
)
}
pub fn analyze_defined_name_with_options(
workbook: &WorkbookSnapshot,
name: &str,
current_sheet: Option<SheetId>,
options: DefinedNameAnalysisOptions,
) -> Result<DefinedNameAnalysis, DefinedNameAnalysisError> {
analyzer::analyze(workbook, name, current_sheet, options, &|| false)
}
pub fn analyze_defined_name_cancellable(
workbook: &WorkbookSnapshot,
name: &str,
current_sheet: Option<SheetId>,
options: DefinedNameAnalysisOptions,
cancellation: &CancellationToken,
) -> Result<DefinedNameAnalysis, DefinedNameAnalysisError> {
analyzer::analyze(workbook, name, current_sheet, options, &|| {
cancellation.is_cancelled()
})
}