use std::sync::Arc;
use super::decimal::DecimalTrace;
use super::functions::BuiltinCallable;
use super::operators::broadcast_index;
use super::runtime::{Array, ReferenceValue};
use super::value::{ErrorKind, Value};
use crate::{DefinedName, DefinedNameScope, SheetId, WorkbookSnapshot};
pub(super) fn resolve_defined_name_scoped<'a>(
workbook: &'a WorkbookSnapshot,
ambient_sheet: Option<SheetId>,
lookup_scope: Option<DefinedNameScope>,
name: &str,
) -> Option<(usize, &'a DefinedName)> {
match lookup_scope {
Some(DefinedNameScope::Workbook) => workbook.defined_name(DefinedNameScope::Workbook, name),
Some(DefinedNameScope::Sheet(sheet_id)) => workbook
.defined_name(DefinedNameScope::Sheet(sheet_id), name)
.or_else(|| workbook.defined_name(DefinedNameScope::Workbook, name)),
None => ambient_sheet
.and_then(|sheet_id| workbook.defined_name(DefinedNameScope::Sheet(sheet_id), name))
.or_else(|| workbook.defined_name(DefinedNameScope::Workbook, name)),
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct ScalarEvaluation {
pub(super) value: Value,
pub(super) decimal_trace: Option<DecimalTrace>,
}
impl ScalarEvaluation {
pub(super) const fn untracked(value: Value) -> Self {
Self {
value,
decimal_trace: None,
}
}
pub(super) fn engine_issue(&self) -> Option<ErrorKind> {
match self.value {
Value::Error(kind) if kind.is_engine_issue() => Some(kind),
_ => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct ArrayEvaluation {
pub(super) array: Array,
pub(super) decimal_traces: Vec<Option<DecimalTrace>>,
}
impl ArrayEvaluation {
pub(super) fn decimal_at(&self, row: u32, column: u32) -> Option<DecimalTrace> {
let index = broadcast_index(self.array.rows, self.array.cols, row, column)?;
self.decimal_traces.get(index).copied().flatten()
}
pub(super) fn untracked(array: Array) -> Self {
let decimal_traces = vec![None; array.data.len()];
Self {
array,
decimal_traces,
}
}
pub(super) fn scalar(evaluated: ScalarEvaluation) -> Self {
Self {
array: Array::scalar(evaluated.value),
decimal_traces: vec![evaluated.decimal_trace],
}
}
pub(super) fn engine_issue(&self) -> Option<ErrorKind> {
self.array.data.iter().find_map(|value| match value {
Value::Error(kind) if kind.is_engine_issue() => Some(*kind),
_ => None,
})
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) enum ScopeValue {
Missing,
Scalar(ScalarEvaluation),
Array(Arc<ArrayEvaluation>),
Reference(ReferenceValue),
Callable(CallableValue),
}
impl ScopeValue {
pub(super) fn engine_issue(&self) -> Option<ErrorKind> {
match self {
Self::Scalar(evaluated) => evaluated.engine_issue(),
Self::Array(evaluated) => evaluated.engine_issue(),
Self::Missing | Self::Reference(_) | Self::Callable(_) => None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) enum CallableValue {
Lambda(Arc<LambdaClosure>),
Builtin(BuiltinCallable),
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct LambdaClosure {
pub(super) parameters: Vec<String>,
pub(super) body: super::ast::Expr,
pub(super) captured: Vec<ScopeEntry>,
pub(super) lookup_scope: Option<DefinedNameScope>,
pub(super) defined_name: Option<DefinedLambdaId>,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub(super) struct DefinedLambdaId {
scope: DefinedNameScope,
lookup_key: Box<str>,
}
impl DefinedLambdaId {
pub(super) fn from_defined_name(defined_name: &DefinedName) -> Self {
Self {
scope: defined_name.scope(),
lookup_key: defined_name.lookup_key().into(),
}
}
pub(super) const fn scope(&self) -> DefinedNameScope {
self.scope
}
}
#[derive(Debug, Clone, PartialEq)]
pub(super) struct ScopeEntry {
name: Box<str>,
value: Arc<ScopeValue>,
}
impl ScopeEntry {
pub(super) fn new(name: String, value: ScopeValue) -> Self {
Self {
name: name.into_boxed_str(),
value: Arc::new(value),
}
}
fn matches(&self, name: &str) -> bool {
self.name.as_ref() == canonical_local_name(name)
}
fn value(&self) -> &ScopeValue {
self.value.as_ref()
}
}
pub(super) fn scope_value<'scope>(
entries: &'scope [ScopeEntry],
name: &str,
) -> Option<&'scope ScopeValue> {
entries
.iter()
.rev()
.find(|entry| entry.matches(name))
.map(ScopeEntry::value)
}
pub(super) fn canonical_local_name(name: &str) -> String {
local_name_base(name).to_lowercase()
}
fn local_name_base(name: &str) -> &str {
name.get(..6)
.filter(|prefix| prefix.eq_ignore_ascii_case("_xlpm."))
.map_or(name, |_| &name[6..])
}