use crate::{
CellRef,
broadcast::{broadcast_shape, project_index},
coercion,
traits::{ArgumentHandle, DefaultFunctionContext, EvaluationContext},
};
use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
use rustc_hash::FxHashMap;
use std::{borrow::Cow, sync::Arc};
use crate::engine::arena::ast::SheetKey;
use crate::engine::arena::{AstNodeData, AstNodeId, CompactRefType, DataStore};
use crate::engine::sheet_registry::SheetRegistry;
use crate::formula_plane::template_canonical::LiteralSlotId;
#[derive(Clone)]
pub enum LocalBinding {
Value(LiteralValue),
Callable(Arc<dyn crate::traits::CustomCallable>),
}
#[derive(Clone, Default)]
pub struct LocalEnv {
head: Option<Arc<EnvFrame>>,
}
#[derive(Clone)]
struct EnvFrame {
parent: Option<Arc<EnvFrame>>,
bindings: FxHashMap<String, LocalBinding>,
}
impl LocalEnv {
#[inline(always)]
pub fn is_empty(&self) -> bool {
self.head.is_none()
}
fn norm(name: &str) -> String {
name.to_ascii_uppercase()
}
pub fn lookup(&self, name: &str) -> Option<LocalBinding> {
self.head.as_ref()?;
let key = Self::norm(name);
let mut cur = self.head.as_ref().cloned();
while let Some(frame) = cur {
if let Some(v) = frame.bindings.get(&key) {
return Some(v.clone());
}
cur = frame.parent.clone();
}
None
}
pub fn with_binding(&self, name: &str, value: LocalBinding) -> Self {
let mut bindings = FxHashMap::default();
bindings.insert(Self::norm(name), value);
Self {
head: Some(Arc::new(EnvFrame {
parent: self.head.clone(),
bindings,
})),
}
}
}
#[derive(Clone, Copy)]
pub(crate) struct InterpreterParameterBindings<'a> {
pub(crate) literal_slots_by_node: &'a FxHashMap<AstNodeId, LiteralSlotId>,
pub(crate) literal_values: &'a [LiteralValue],
}
pub struct Interpreter<'a> {
pub context: &'a dyn EvaluationContext,
current_sheet: &'a str,
current_cell: Option<crate::CellRef>,
local_env: LocalEnv,
reference_row_delta: i64,
reference_col_delta: i64,
disable_ast_planner: bool,
parameter_bindings: Option<InterpreterParameterBindings<'a>>,
}
impl<'a> Interpreter<'a> {
pub fn new(context: &'a dyn EvaluationContext, current_sheet: &'a str) -> Self {
Self {
context,
current_sheet,
current_cell: None,
local_env: LocalEnv::default(),
reference_row_delta: 0,
reference_col_delta: 0,
disable_ast_planner: false,
parameter_bindings: None,
}
}
pub fn new_with_cell(
context: &'a dyn EvaluationContext,
current_sheet: &'a str,
cell: crate::CellRef,
) -> Self {
Self {
context,
current_sheet,
current_cell: Some(cell),
local_env: LocalEnv::default(),
reference_row_delta: 0,
reference_col_delta: 0,
disable_ast_planner: false,
parameter_bindings: None,
}
}
pub fn current_sheet(&self) -> &'a str {
self.current_sheet
}
pub fn local_env(&self) -> &LocalEnv {
&self.local_env
}
pub(crate) fn with_current_cell(&self, cell: crate::CellRef) -> Self {
Self {
context: self.context,
current_sheet: self.current_sheet,
current_cell: Some(cell),
local_env: self.local_env.clone(),
reference_row_delta: self.reference_row_delta,
reference_col_delta: self.reference_col_delta,
disable_ast_planner: self.disable_ast_planner,
parameter_bindings: self.parameter_bindings,
}
}
pub fn with_local_env(&self, env: LocalEnv) -> Self {
Self {
context: self.context,
current_sheet: self.current_sheet,
current_cell: self.current_cell,
local_env: env,
reference_row_delta: self.reference_row_delta,
reference_col_delta: self.reference_col_delta,
disable_ast_planner: self.disable_ast_planner,
parameter_bindings: self.parameter_bindings,
}
}
pub(crate) fn with_parameter_bindings(
&self,
bindings: InterpreterParameterBindings<'a>,
) -> Self {
Self {
context: self.context,
current_sheet: self.current_sheet,
current_cell: self.current_cell,
local_env: self.local_env.clone(),
reference_row_delta: self.reference_row_delta,
reference_col_delta: self.reference_col_delta,
disable_ast_planner: self.disable_ast_planner,
parameter_bindings: Some(bindings),
}
}
fn effective_reference<'r>(
&self,
reference: &'r ReferenceType,
) -> Result<Cow<'r, ReferenceType>, ExcelError> {
if self.reference_row_delta == 0 && self.reference_col_delta == 0 {
return Ok(Cow::Borrowed(reference));
}
Ok(Cow::Owned(relocate_reference_for_offset(
reference,
self.reference_row_delta,
self.reference_col_delta,
)?))
}
fn resolve_local_reference(
&self,
reference: &ReferenceType,
) -> Option<crate::traits::CalcValue<'a>> {
if self.local_env.is_empty() {
return None;
}
let name = match reference {
ReferenceType::NamedRange(name) => name,
_ => return None,
};
match self.local_env.lookup(name)? {
LocalBinding::Value(v) => Some(crate::traits::CalcValue::Scalar(v)),
LocalBinding::Callable(c) => Some(crate::traits::CalcValue::Callable(c)),
}
}
fn resolve_local_callable(&self, name: &str) -> Option<Arc<dyn crate::traits::CustomCallable>> {
if self.local_env.is_empty() {
return None;
}
match self.local_env.lookup(name)? {
LocalBinding::Callable(c) => Some(c),
LocalBinding::Value(_) => None,
}
}
pub fn resolve_local_name(&self, name: &str) -> Option<LocalBinding> {
self.local_env.lookup(name)
}
pub fn resolve_range_view<'c>(
&'c self,
reference: &ReferenceType,
current_sheet: &str,
) -> Result<crate::engine::range_view::RangeView<'c>, ExcelError> {
self.context.resolve_range_view(reference, current_sheet)
}
pub fn evaluate_ast_as_reference(&self, node: &ASTNode) -> Result<ReferenceType, ExcelError> {
match &node.node_type {
ASTNodeType::Reference { reference, .. } => {
self.reference_for_current_offset(reference)
}
ASTNodeType::Function { name, args } => {
if let Some(fun) = self.context.get_function("", name) {
let handles: Vec<ArgumentHandle> =
args.iter().map(|n| ArgumentHandle::new(n, self)).collect();
let fctx = DefaultFunctionContext::new_with_sheet(
self.context,
None,
self.current_sheet,
);
if let Some(res) = fun.eval_reference(&handles, &fctx) {
res
} else {
Err(ExcelError::new(ExcelErrorKind::Ref)
.with_message("Function does not return a reference"))
}
} else {
Err(ExcelError::new(ExcelErrorKind::Name)
.with_message(format!("Unknown function: {name}")))
}
}
ASTNodeType::BinaryOp { op, left, right } if op == ":" => {
let lref = self.evaluate_ast_as_reference(left)?;
let rref = self.evaluate_ast_as_reference(right)?;
crate::reference::combine_references(&lref, &rref)
}
ASTNodeType::Array(_)
| ASTNodeType::UnaryOp { .. }
| ASTNodeType::BinaryOp { .. }
| ASTNodeType::Call { .. }
| ASTNodeType::Literal(_) => Err(ExcelError::new(ExcelErrorKind::Ref)
.with_message("Expression cannot be used as a reference")),
}
}
pub(crate) fn evaluate_arena_ast_as_reference(
&self,
node_id: AstNodeId,
data_store: &DataStore,
sheet_registry: &SheetRegistry,
) -> Result<ReferenceType, ExcelError> {
let node = data_store.get_node(node_id).ok_or_else(|| {
ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
})?;
match node {
AstNodeData::Reference { ref_type, .. } => {
let reference =
data_store.reconstruct_reference_type_for_eval(ref_type, sheet_registry);
self.reference_for_current_offset(&reference)
}
AstNodeData::Function { name_id, .. } => {
let name = data_store.resolve_ast_string(*name_id);
let fun = self.context.get_function("", name).ok_or_else(|| {
ExcelError::new(ExcelErrorKind::Name)
.with_message(format!("Unknown function: {name}"))
})?;
let args = data_store.get_args(node_id).ok_or_else(|| {
ExcelError::new(ExcelErrorKind::Value).with_message("Missing function args")
})?;
let handles: Vec<ArgumentHandle> = args
.iter()
.copied()
.map(|arg_id| {
ArgumentHandle::new_arena(arg_id, self, data_store, sheet_registry)
})
.collect();
let fctx =
DefaultFunctionContext::new_with_sheet(self.context, None, self.current_sheet);
fun.eval_reference(&handles, &fctx).ok_or_else(|| {
ExcelError::new(ExcelErrorKind::Ref)
.with_message("Function does not return a reference")
})?
}
AstNodeData::BinaryOp {
op_id,
left_id,
right_id,
} => {
let op = data_store.resolve_ast_string(*op_id);
if op != ":" {
return Err(ExcelError::new(ExcelErrorKind::Ref)
.with_message("Expression cannot be used as a reference"));
}
let lref =
self.evaluate_arena_ast_as_reference(*left_id, data_store, sheet_registry)?;
let rref =
self.evaluate_arena_ast_as_reference(*right_id, data_store, sheet_registry)?;
crate::reference::combine_references(&lref, &rref)
}
_ => Err(ExcelError::new(ExcelErrorKind::Ref)
.with_message("Expression cannot be used as a reference")),
}
}
pub fn evaluate_ast(&self, node: &ASTNode) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
self.evaluate_ast_uncached(node)
}
pub(crate) fn evaluate_ast_with_offset(
&self,
node: &ASTNode,
row_delta: i64,
col_delta: i64,
) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
let offset = Self {
context: self.context,
current_sheet: self.current_sheet,
current_cell: self.current_cell,
local_env: self.local_env.clone(),
reference_row_delta: row_delta,
reference_col_delta: col_delta,
disable_ast_planner: true,
parameter_bindings: self.parameter_bindings,
};
offset.evaluate_ast_uncached(node)
}
pub(crate) fn reference_for_current_offset(
&self,
reference: &ReferenceType,
) -> Result<ReferenceType, ExcelError> {
self.effective_reference(reference)
.map(|reference| reference.into_owned())
}
pub(crate) fn evaluate_arena_ast_with_offset(
&self,
node_id: AstNodeId,
row_delta: i64,
col_delta: i64,
data_store: &DataStore,
sheet_registry: &SheetRegistry,
) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
let offset = Self {
context: self.context,
current_sheet: self.current_sheet,
current_cell: self.current_cell,
local_env: self.local_env.clone(),
reference_row_delta: row_delta,
reference_col_delta: col_delta,
disable_ast_planner: true,
parameter_bindings: self.parameter_bindings,
};
offset.evaluate_arena_ast(node_id, data_store, sheet_registry)
}
pub(crate) fn evaluate_arena_ast(
&self,
node_id: AstNodeId,
data_store: &DataStore,
sheet_registry: &SheetRegistry,
) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
let node = data_store.get_node(node_id).ok_or_else(|| {
ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
})?;
match node {
AstNodeData::Literal(vref) => {
if let Some(bindings) = self.parameter_bindings
&& let Some(slot_id) = bindings.literal_slots_by_node.get(&node_id)
&& let Some(value) = bindings.literal_values.get(slot_id.0 as usize)
{
return Ok(crate::traits::CalcValue::Scalar(value.clone()));
}
Ok(crate::traits::CalcValue::Scalar(
data_store.retrieve_value(*vref),
))
}
AstNodeData::Reference { ref_type, .. } => {
if self.local_env.is_empty()
&& let CompactRefType::Cell {
sheet,
row,
col,
row_abs,
col_abs,
} = ref_type
&& *row > 0
&& *col > 0
{
let sheet_name = match sheet {
Some(SheetKey::Id(id)) => Some(sheet_registry.name(*id)),
Some(SheetKey::Name(name_id)) => {
Some(data_store.resolve_ast_string(*name_id))
}
None => None,
};
let row = shift_axis_for_offset(*row, self.reference_row_delta, *row_abs)?;
let col = shift_axis_for_offset(*col, self.reference_col_delta, *col_abs)?;
let value = self.context.resolve_cell_reference_value(
sheet_name,
row,
col,
self.current_sheet,
)?;
Ok(crate::traits::CalcValue::Scalar(value))
} else {
let reference =
data_store.reconstruct_reference_type_for_eval(ref_type, sheet_registry);
let reference = self.effective_reference(&reference)?;
if let Some(local) = self.resolve_local_reference(&reference) {
return Ok(local);
}
self.eval_reference_to_calc(&reference)
}
}
AstNodeData::UnaryOp { op_id, expr_id } => {
let expr = self.evaluate_arena_ast(*expr_id, data_store, sheet_registry)?;
let op = data_store.resolve_ast_string(*op_id);
if op == "@" {
if let Some(AstNodeData::Reference { ref_type, .. }) =
data_store.get_node(*expr_id)
{
let reference = data_store
.reconstruct_reference_type_for_eval(ref_type, sheet_registry);
let v = self.implicit_intersection_from_reference(&reference);
return Ok(crate::traits::CalcValue::Scalar(v));
}
let v = self.eval_implicit_intersection_calc(expr);
return Ok(crate::traits::CalcValue::Scalar(v));
}
let v = expr.into_literal();
match v {
LiteralValue::Array(arr) => self
.map_array(arr, |cell| self.eval_unary_scalar(op, cell))
.map(crate::traits::CalcValue::Scalar),
other => self
.eval_unary_scalar(op, other)
.map(crate::traits::CalcValue::Scalar),
}
}
AstNodeData::BinaryOp {
op_id,
left_id,
right_id,
} => {
let op = data_store.resolve_ast_string(*op_id);
if op == ":" {
let lref =
self.evaluate_arena_ast_as_reference(*left_id, data_store, sheet_registry)?;
let rref = self.evaluate_arena_ast_as_reference(
*right_id,
data_store,
sheet_registry,
)?;
return match crate::reference::combine_references(&lref, &rref) {
Ok(_r) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new(ExcelErrorKind::Ref).with_message(
"Reference produced by ':' cannot be used directly as a value",
),
))),
Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
};
}
let left = self
.evaluate_arena_ast(*left_id, data_store, sheet_registry)?
.into_literal();
let right = self
.evaluate_arena_ast(*right_id, data_store, sheet_registry)?
.into_literal();
if matches!(op, "=" | "<>" | ">" | "<" | ">=" | "<=") {
return self
.compare(op, left, right)
.map(crate::traits::CalcValue::Scalar);
}
match op {
"+" => self
.add_sub_date_aware('+', left, right)
.map(crate::traits::CalcValue::Scalar),
"-" => self
.add_sub_date_aware('-', left, right)
.map(crate::traits::CalcValue::Scalar),
"*" => self
.numeric_binary(left, right, |a, b| a * b)
.map(crate::traits::CalcValue::Scalar),
"/" => self
.divide(left, right)
.map(crate::traits::CalcValue::Scalar),
"^" => self
.power(left, right)
.map(crate::traits::CalcValue::Scalar),
"&" => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
format!(
"{}{}",
crate::coercion::to_text_invariant(&left),
crate::coercion::to_text_invariant(&right)
),
))),
_ => Err(ExcelError::new(ExcelErrorKind::NImpl)
.with_message(format!("Binary op '{op}'"))),
}
}
AstNodeData::Array { .. } => {
let (rows, cols, elements) =
data_store.get_array_elems(node_id).ok_or_else(|| {
ExcelError::new(ExcelErrorKind::Value).with_message("Invalid array")
})?;
let rows_usize = rows as usize;
let cols_usize = cols as usize;
let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows_usize);
for r in 0..rows_usize {
let mut row = Vec::with_capacity(cols_usize);
for c in 0..cols_usize {
let idx = r * cols_usize + c;
if let Some(&elem_id) = elements.get(idx) {
row.push(
self.evaluate_arena_ast(elem_id, data_store, sheet_registry)?
.into_literal(),
);
}
}
out.push(row);
}
Ok(crate::traits::CalcValue::Range(
crate::engine::range_view::RangeView::from_owned_rows(
out,
self.context.date_system(),
),
))
}
AstNodeData::Function { name_id, .. } => {
let name = data_store.resolve_ast_string(*name_id);
let args = data_store.get_args(node_id).ok_or_else(|| {
ExcelError::new(ExcelErrorKind::Value).with_message("Missing function args")
})?;
if let Some(fun) = self.context.get_function("", name) {
let handles: Vec<ArgumentHandle> = args
.iter()
.copied()
.map(|arg_id| {
ArgumentHandle::new_arena(arg_id, self, data_store, sheet_registry)
})
.collect();
let fctx = DefaultFunctionContext::new_with_sheet(
self.context,
self.current_cell,
self.current_sheet,
);
return fun.dispatch(&handles, &fctx);
}
if let Some(callable) = self.resolve_local_callable(name) {
let mut eval_args = Vec::with_capacity(args.len());
for arg_id in args {
eval_args.push(
self.evaluate_arena_ast(*arg_id, data_store, sheet_registry)?
.into_literal(),
);
}
return callable.invoke(self, &eval_args);
}
Err(ExcelError::new(ExcelErrorKind::Name)
.with_message(format!("Unknown function: {name}")))
}
}
}
fn evaluate_ast_uncached(
&self,
node: &ASTNode,
) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
if self.disable_ast_planner {
return self.eval_tree_uncached(node);
}
let current_sheet = self.current_sheet.to_string();
let range_probe = |reference: &ReferenceType| -> Option<(u32, u32)> {
use formualizer_parse::parser::ReferenceType as RT;
match reference {
RT::Range {
sheet,
start_row,
start_col,
end_row,
end_col,
..
} => {
let sheet_name = sheet.as_deref().unwrap_or(¤t_sheet);
let mut sr = *start_row;
let mut sc = *start_col;
let mut er = *end_row;
let mut ec = *end_col;
if sr.is_none() && er.is_none() {
let scv = sc.unwrap_or(1);
let ecv = ec.unwrap_or(scv);
sr = Some(1);
if let Some((_, max_r)) =
self.context.used_rows_for_columns(sheet_name, scv, ecv)
{
er = Some(max_r);
} else if let Some((max_rows, _)) = self.context.sheet_bounds(sheet_name) {
er = Some(max_rows);
}
}
if sc.is_none() && ec.is_none() {
let srv = sr.unwrap_or(1);
let erv = er.unwrap_or(srv);
sc = Some(1);
if let Some((_, max_c)) =
self.context.used_cols_for_rows(sheet_name, srv, erv)
{
ec = Some(max_c);
} else if let Some((_, max_cols)) = self.context.sheet_bounds(sheet_name) {
ec = Some(max_cols);
}
}
if sr.is_some() && er.is_none() {
let scv = sc.unwrap_or(1);
let ecv = ec.unwrap_or(scv);
if let Some((_, max_r)) =
self.context.used_rows_for_columns(sheet_name, scv, ecv)
{
er = Some(max_r);
} else if let Some((max_rows, _)) = self.context.sheet_bounds(sheet_name) {
er = Some(max_rows);
}
}
if er.is_some() && sr.is_none() {
sr = Some(1);
}
if sc.is_some() && ec.is_none() {
let srv = sr.unwrap_or(1);
let erv = er.unwrap_or(srv);
if let Some((_, max_c)) =
self.context.used_cols_for_rows(sheet_name, srv, erv)
{
ec = Some(max_c);
} else if let Some((_, max_cols)) = self.context.sheet_bounds(sheet_name) {
ec = Some(max_cols);
}
}
if ec.is_some() && sc.is_none() {
sc = Some(1);
}
let sr = sr.unwrap_or(1);
let sc = sc.unwrap_or(1);
let er = er.unwrap_or(sr.saturating_sub(1));
let ec = ec.unwrap_or(sc.saturating_sub(1));
if er < sr || ec < sc {
return Some((0, 0));
}
Some((er.saturating_sub(sr) + 1, ec.saturating_sub(sc) + 1))
}
RT::Cell { .. } => Some((1, 1)),
_ => None,
}
};
let fn_lookup = |ns: &str, name: &str| self.context.get_function(ns, name);
let mut planner = crate::planner::Planner::new(crate::planner::PlanConfig::default())
.with_range_probe(&range_probe)
.with_function_lookup(&fn_lookup);
let plan = planner.plan(node);
self.eval_with_plan(node, &plan.root)
}
fn eval_tree_uncached(
&self,
node: &ASTNode,
) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
match &node.node_type {
ASTNodeType::Literal(v) => Ok(crate::traits::CalcValue::Scalar(v.clone())),
ASTNodeType::Reference { reference, .. } => self.eval_ast_reference_to_calc(reference),
ASTNodeType::UnaryOp { op, expr } => self
.eval_unary(op, expr)
.map(crate::traits::CalcValue::Scalar),
ASTNodeType::BinaryOp { op, left, right } => self
.eval_binary(op, left, right)
.map(crate::traits::CalcValue::Scalar),
ASTNodeType::Function { name, args } => self.eval_function_to_calc(name, args),
ASTNodeType::Call { .. } => Err(ExcelError::new(ExcelErrorKind::NImpl)
.with_message("Immediate-invocation calls are not yet supported")),
ASTNodeType::Array(rows) => self.eval_array_literal_to_calc(rows),
}
}
fn eval_with_plan(
&self,
node: &ASTNode,
plan_node: &crate::planner::PlanNode,
) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
match &node.node_type {
ASTNodeType::Literal(v) => Ok(crate::traits::CalcValue::Scalar(v.clone())),
ASTNodeType::Reference { reference, .. } => self.eval_ast_reference_to_calc(reference),
ASTNodeType::UnaryOp { op, expr } => {
self.eval_unary(op, expr)
.map(crate::traits::CalcValue::Scalar)
}
ASTNodeType::BinaryOp { op, left, right } => self
.eval_binary(op, left, right)
.map(crate::traits::CalcValue::Scalar),
ASTNodeType::Function { name, args } => {
let strategy = plan_node.strategy;
if let Some(fun) = self.context.get_function("", name) {
use crate::function::FnCaps;
use crate::planner::ExecStrategy;
let caps = fun.caps();
if caps.contains(FnCaps::SHORT_CIRCUIT) || caps.contains(FnCaps::VOLATILE) {
return self.eval_function_to_calc(name, args);
}
if matches!(strategy, ExecStrategy::ArgParallel)
&& caps.contains(FnCaps::PARALLEL_ARGS)
{
for arg in args {
match &arg.node_type {
ASTNodeType::Reference { reference, .. } => {
if let Ok(reference) = self.effective_reference(reference) {
let _ = self
.context
.resolve_range_view(&reference, self.current_sheet);
}
}
_ => {
let _ = self.evaluate_ast(arg);
}
}
}
return self.eval_function_to_calc(name, args);
}
return self.eval_function_to_calc(name, args);
}
self.eval_function_to_calc(name, args)
}
ASTNodeType::Call { .. } => Err(ExcelError::new(ExcelErrorKind::NImpl)
.with_message("Immediate-invocation calls are not yet supported")),
ASTNodeType::Array(rows) => self.eval_array_literal_to_calc(rows),
}
}
fn eval_ast_reference_to_calc(
&self,
reference: &ReferenceType,
) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
if !self.local_env.is_empty() {
let reference = self.effective_reference(reference)?;
if let Some(local) = self.resolve_local_reference(&reference) {
return Ok(local);
}
return self.eval_reference_to_calc(&reference);
}
if let ReferenceType::Cell {
sheet,
row,
col,
row_abs,
col_abs,
} = reference
{
let row = shift_axis_for_offset(*row, self.reference_row_delta, *row_abs)?;
let col = shift_axis_for_offset(*col, self.reference_col_delta, *col_abs)?;
return Ok(crate::traits::CalcValue::Scalar(
self.context.resolve_cell_reference_value(
sheet.as_deref(),
row,
col,
self.current_sheet,
)?,
));
}
let reference = self.effective_reference(reference)?;
self.eval_reference_to_calc(&reference)
}
fn eval_reference_to_calc(
&self,
reference: &ReferenceType,
) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
if let ReferenceType::Cell {
sheet, row, col, ..
} = reference
{
return Ok(crate::traits::CalcValue::Scalar(
self.context.resolve_cell_reference_value(
sheet.as_deref(),
*row,
*col,
self.current_sheet,
)?,
));
}
let view = self
.context
.resolve_range_view(reference, self.current_sheet)?
.with_cancel_token(self.context.cancellation_token());
Ok(crate::traits::CalcValue::Range(view))
}
fn eval_reference(&self, reference: &ReferenceType) -> Result<LiteralValue, ExcelError> {
self.eval_reference_to_calc(reference)
.map(|cv| cv.into_literal())
}
fn eval_unary(&self, op: &str, expr: &ASTNode) -> Result<LiteralValue, ExcelError> {
if op == "@" {
if let ASTNodeType::Reference { reference, .. } = &expr.node_type {
let reference = self.effective_reference(reference)?;
return Ok(self.implicit_intersection_from_reference(&reference));
}
let cv = self.evaluate_ast(expr)?;
return Ok(self.eval_implicit_intersection_calc(cv));
}
let v = self.evaluate_ast(expr)?.into_literal();
match v {
LiteralValue::Array(arr) => {
self.map_array(arr, |cell| self.eval_unary_scalar(op, cell))
}
other => self.eval_unary_scalar(op, other),
}
}
fn eval_unary_scalar(&self, op: &str, v: LiteralValue) -> Result<LiteralValue, ExcelError> {
match op {
"+" => Ok(v),
"-" => self.apply_number_unary(v, |n| -n),
"%" => self.apply_number_unary(v, |n| n / 100.0),
_ => {
Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!("Unary op '{op}'")))
}
}
}
fn eval_implicit_intersection_calc(&self, cv: crate::traits::CalcValue<'a>) -> LiteralValue {
let (cur_r0, cur_c0) = match self.current_cell {
Some(cell) => (cell.coord.row() as usize, cell.coord.col() as usize),
None => (0usize, 0usize),
};
match cv {
crate::traits::CalcValue::Scalar(v) => match v {
LiteralValue::Array(arr) => {
if arr.is_empty() || arr.first().map(|r| r.is_empty()).unwrap_or(true) {
return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
}
arr[0][0].clone()
}
other => other,
},
crate::traits::CalcValue::Range(rv) => {
if rv.is_empty() {
return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
}
if rv.sheet_name() == "__tmp" {
return rv.get_cell(0, 0);
}
if let Some(v) = rv.as_1x1() {
return v;
}
let (rows, cols) = rv.dims();
let sr = rv.start_row();
let sc = rv.start_col();
let er = rv.end_row();
let ec = rv.end_col();
if cols == 1 {
if cur_r0 < sr || cur_r0 > er {
return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
}
let rel_r = cur_r0 - sr;
return rv.get_cell(rel_r, 0);
}
if rows == 1 {
if cur_c0 < sc || cur_c0 > ec {
return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
}
let rel_c = cur_c0 - sc;
return rv.get_cell(0, rel_c);
}
if cur_r0 < sr || cur_r0 > er || cur_c0 < sc || cur_c0 > ec {
return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
}
let rel_r = cur_r0 - sr;
let rel_c = cur_c0 - sc;
rv.get_cell(rel_r, rel_c)
}
crate::traits::CalcValue::Callable(_) => LiteralValue::Error(
ExcelError::new(ExcelErrorKind::Calc).with_message("LAMBDA value must be invoked"),
),
}
}
fn implicit_intersection_from_reference(&self, reference: &ReferenceType) -> LiteralValue {
let (cur_r1, cur_c1) = match self.current_cell {
Some(cell) => (
cell.coord.row().saturating_add(1),
cell.coord.col().saturating_add(1),
),
None => (1u32, 1u32),
};
match reference {
ReferenceType::Cell {
sheet, row, col, ..
} => {
let sheet_name = sheet.as_deref().unwrap_or(self.current_sheet);
match self
.context
.resolve_cell_reference(Some(sheet_name), *row, *col)
{
Ok(v) => v,
Err(e) => LiteralValue::Error(e),
}
}
ReferenceType::Range {
sheet,
start_row,
start_col,
end_row,
end_col,
..
} => {
let sheet_name = sheet.as_deref().unwrap_or(self.current_sheet);
let (sr, sc, er, ec) = match (start_row, start_col, end_row, end_col) {
(Some(sr), Some(sc), Some(er), Some(ec)) => (*sr, *sc, *er, *ec),
_ => {
let cv = match self.eval_reference_to_calc(reference) {
Ok(cv) => cv,
Err(e) => return LiteralValue::Error(e),
};
return self.eval_implicit_intersection_calc(cv);
}
};
let (mut sr, mut er) = (sr, er);
let (mut sc, mut ec) = (sc, ec);
if sr > er {
std::mem::swap(&mut sr, &mut er);
}
if sc > ec {
std::mem::swap(&mut sc, &mut ec);
}
let pick = if sc == ec {
if cur_r1 < sr || cur_r1 > er {
return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
}
(cur_r1, sc)
} else if sr == er {
if cur_c1 < sc || cur_c1 > ec {
return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
}
(sr, cur_c1)
} else {
if cur_r1 < sr || cur_r1 > er || cur_c1 < sc || cur_c1 > ec {
return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
}
(cur_r1, cur_c1)
};
match self
.context
.resolve_cell_reference(Some(sheet_name), pick.0, pick.1)
{
Ok(v) => v,
Err(e) => LiteralValue::Error(e),
}
}
other => {
let cv = match self.eval_reference_to_calc(other) {
Ok(cv) => cv,
Err(e) => return LiteralValue::Error(e),
};
self.eval_implicit_intersection_calc(cv)
}
}
}
fn apply_number_unary<F>(&self, v: LiteralValue, f: F) -> Result<LiteralValue, ExcelError>
where
F: Fn(f64) -> f64,
{
match crate::coercion::to_number_lenient_with_locale(&v, &self.context.locale()) {
Ok(n) => match crate::coercion::sanitize_numeric(f(n)) {
Ok(n2) => Ok(LiteralValue::Number(n2)),
Err(e) => Ok(LiteralValue::Error(e)),
},
Err(e) => Ok(LiteralValue::Error(e)),
}
}
fn eval_binary(
&self,
op: &str,
left: &ASTNode,
right: &ASTNode,
) -> Result<LiteralValue, ExcelError> {
if matches!(op, "=" | "<>" | ">" | "<" | ">=" | "<=") {
let l = self.evaluate_ast(left)?.into_literal();
let r = self.evaluate_ast(right)?.into_literal();
return self.compare(op, l, r);
}
let l_val = self.evaluate_ast(left)?.into_literal();
let r_val = self.evaluate_ast(right)?.into_literal();
match op {
"+" => self.add_sub_date_aware('+', l_val, r_val),
"-" => self.add_sub_date_aware('-', l_val, r_val),
"*" => self.numeric_binary(l_val, r_val, |a, b| a * b),
"/" => self.divide(l_val, r_val),
"^" => self.power(l_val, r_val),
"&" => Ok(LiteralValue::Text(format!(
"{}{}",
crate::coercion::to_text_invariant(&l_val),
crate::coercion::to_text_invariant(&r_val)
))),
":" => {
let lref = self.evaluate_ast_as_reference(left)?;
let rref = self.evaluate_ast_as_reference(right)?;
match crate::reference::combine_references(&lref, &rref) {
Ok(_r) => Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
"Reference produced by ':' cannot be used directly as a value",
)),
Err(e) => Ok(LiteralValue::Error(e)),
}
}
_ => {
Err(ExcelError::new(ExcelErrorKind::NImpl)
.with_message(format!("Binary op '{op}'")))
}
}
}
fn add_sub_date_aware(
&self,
op: char,
left: LiteralValue,
right: LiteralValue,
) -> Result<LiteralValue, ExcelError> {
debug_assert!(op == '+' || op == '-');
self.broadcast_apply(left, right, |l, r| {
use LiteralValue::*;
let date_system = self.context.date_system();
let date_like_serial = |v: &LiteralValue| -> Option<f64> {
match v {
Date(d) => Some(crate::builtins::datetime::date_to_serial_for(
date_system,
d,
)),
DateTime(dt) => Some(crate::builtins::datetime::datetime_to_serial_for(
date_system,
dt,
)),
_ => None,
}
};
let to_num = |v: &LiteralValue| -> Result<f64, ExcelError> {
crate::coercion::to_number_lenient_with_locale(v, &self.context.locale())
};
let serial_to_literal = |serial: f64| -> LiteralValue {
match crate::coercion::sanitize_numeric(serial) {
Ok(serial) => {
match crate::builtins::datetime::serial_to_datetime_for(date_system, serial)
{
Ok(dt) => {
if dt.time() == chrono::NaiveTime::from_hms_opt(0, 0, 0).unwrap() {
Date(dt.date())
} else {
DateTime(dt)
}
}
Err(e) => Error(e),
}
}
Err(e) => Error(e),
}
};
if let Some(ls) = date_like_serial(&l) {
match op {
'+' => {
let rn = to_num(&r)?;
return Ok(serial_to_literal(ls + rn));
}
'-' => {
if let Some(rs) = date_like_serial(&r) {
return Ok(Number(ls - rs));
}
let rn = to_num(&r)?;
return Ok(serial_to_literal(ls - rn));
}
_ => unreachable!(),
}
}
if op == '+'
&& let Some(rs) = date_like_serial(&r)
{
let ln = to_num(&l)?;
return Ok(serial_to_literal(ln + rs));
}
self.numeric_binary(l, r, |a, b| if op == '+' { a + b } else { a - b })
})
}
fn eval_function_to_calc(
&self,
name: &str,
args: &[ASTNode],
) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
if let Some(fun) = self.context.get_function("", name) {
let handles: Vec<ArgumentHandle> =
args.iter().map(|n| ArgumentHandle::new(n, self)).collect();
let fctx = DefaultFunctionContext::new_with_sheet(
self.context,
self.current_cell,
self.current_sheet,
);
return fun.dispatch(&handles, &fctx);
}
if let Some(callable) = self.resolve_local_callable(name) {
let mut eval_args = Vec::with_capacity(args.len());
for arg in args {
eval_args.push(self.evaluate_ast(arg)?.into_literal());
}
return callable.invoke(self, &eval_args);
}
Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
ExcelError::new(ExcelErrorKind::Name).with_message(format!("Unknown function: {name}")),
)))
}
fn eval_function(&self, name: &str, args: &[ASTNode]) -> Result<LiteralValue, ExcelError> {
self.eval_function_to_calc(name, args)
.map(|cv| cv.into_literal())
}
pub fn function_context(&self, cell_ref: Option<&CellRef>) -> DefaultFunctionContext<'_> {
DefaultFunctionContext::new_with_sheet(self.context, cell_ref.cloned(), self.current_sheet)
}
fn eval_array_literal_to_calc(
&self,
rows: &[Vec<ASTNode>],
) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
let mut out = Vec::with_capacity(rows.len());
for row in rows {
let mut r = Vec::with_capacity(row.len());
for cell in row {
r.push(self.evaluate_ast(cell)?.into_literal());
}
out.push(r);
}
Ok(crate::traits::CalcValue::Range(
crate::engine::range_view::RangeView::from_owned_rows(out, self.context.date_system()),
))
}
fn eval_array_literal(&self, rows: &[Vec<ASTNode>]) -> Result<LiteralValue, ExcelError> {
self.eval_array_literal_to_calc(rows)
.map(|cv| cv.into_literal())
}
fn numeric_binary<F>(
&self,
left: LiteralValue,
right: LiteralValue,
f: F,
) -> Result<LiteralValue, ExcelError>
where
F: Fn(f64, f64) -> f64 + Copy,
{
self.broadcast_apply(left, right, |l, r| {
let a = crate::coercion::to_number_lenient_with_locale(&l, &self.context.locale());
let b = crate::coercion::to_number_lenient_with_locale(&r, &self.context.locale());
match (a, b) {
(Ok(a), Ok(b)) => match crate::coercion::sanitize_numeric(f(a, b)) {
Ok(n2) => Ok(LiteralValue::Number(n2)),
Err(e) => Ok(LiteralValue::Error(e)),
},
(Err(e), _) | (_, Err(e)) => Ok(LiteralValue::Error(e)),
}
})
}
fn divide(&self, left: LiteralValue, right: LiteralValue) -> Result<LiteralValue, ExcelError> {
self.broadcast_apply(left, right, |l, r| {
let ln = crate::coercion::to_number_lenient_with_locale(&l, &self.context.locale());
let rn = crate::coercion::to_number_lenient_with_locale(&r, &self.context.locale());
let (a, b) = match (ln, rn) {
(Ok(a), Ok(b)) => (a, b),
(Err(e), _) | (_, Err(e)) => return Ok(LiteralValue::Error(e)),
};
if b == 0.0 {
return Ok(LiteralValue::Error(ExcelError::from_error_string(
"#DIV/0!",
)));
}
match crate::coercion::sanitize_numeric(a / b) {
Ok(n) => Ok(LiteralValue::Number(n)),
Err(e) => Ok(LiteralValue::Error(e)),
}
})
}
fn power(&self, left: LiteralValue, right: LiteralValue) -> Result<LiteralValue, ExcelError> {
self.broadcast_apply(left, right, |l, r| {
let ln = crate::coercion::to_number_lenient_with_locale(&l, &self.context.locale());
let rn = crate::coercion::to_number_lenient_with_locale(&r, &self.context.locale());
let (a, b) = match (ln, rn) {
(Ok(a), Ok(b)) => (a, b),
(Err(e), _) | (_, Err(e)) => return Ok(LiteralValue::Error(e)),
};
if a < 0.0 && b.fract() != 0.0 {
return Ok(LiteralValue::Error(ExcelError::new_num()));
}
match crate::coercion::sanitize_numeric(a.powf(b)) {
Ok(n) => Ok(LiteralValue::Number(n)),
Err(e) => Ok(LiteralValue::Error(e)),
}
})
}
fn map_array<F>(&self, arr: Vec<Vec<LiteralValue>>, f: F) -> Result<LiteralValue, ExcelError>
where
F: Fn(LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
{
let mut out = Vec::with_capacity(arr.len());
for row in arr {
let mut new_row = Vec::with_capacity(row.len());
for cell in row {
new_row.push(match f(cell) {
Ok(v) => v,
Err(e) => LiteralValue::Error(e),
});
}
out.push(new_row);
}
Ok(LiteralValue::Array(out))
}
fn combine_arrays<F>(
&self,
l: Vec<Vec<LiteralValue>>,
r: Vec<Vec<LiteralValue>>,
f: F,
) -> Result<LiteralValue, ExcelError>
where
F: Fn(LiteralValue, LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
{
let l_shape = (l.len(), l.first().map(|r| r.len()).unwrap_or(0));
let r_shape = (r.len(), r.first().map(|r| r.len()).unwrap_or(0));
let target = match broadcast_shape(&[l_shape, r_shape]) {
Ok(s) => s,
Err(e) => return Ok(LiteralValue::Error(e)),
};
let mut out = Vec::with_capacity(target.0);
for i in 0..target.0 {
let mut row = Vec::with_capacity(target.1);
for j in 0..target.1 {
let (li, lj) = project_index((i, j), l_shape);
let (ri, rj) = project_index((i, j), r_shape);
let lv = l
.get(li)
.and_then(|r| r.get(lj))
.cloned()
.unwrap_or(LiteralValue::Empty);
let rv = r
.get(ri)
.and_then(|r| r.get(rj))
.cloned()
.unwrap_or(LiteralValue::Empty);
row.push(match f(lv, rv) {
Ok(v) => v,
Err(e) => LiteralValue::Error(e),
});
}
out.push(row);
}
Ok(LiteralValue::Array(out))
}
fn broadcast_apply<F>(
&self,
left: LiteralValue,
right: LiteralValue,
f: F,
) -> Result<LiteralValue, ExcelError>
where
F: Fn(LiteralValue, LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
{
use LiteralValue::*;
match (left, right) {
(Array(l), Array(r)) => self.combine_arrays(l, r, f),
(Array(arr), v) => {
let shape_l = (arr.len(), arr.first().map(|r| r.len()).unwrap_or(0));
let shape_r = (1usize, 1usize);
let target = match broadcast_shape(&[shape_l, shape_r]) {
Ok(s) => s,
Err(e) => return Ok(LiteralValue::Error(e)),
};
let mut out = Vec::with_capacity(target.0);
for i in 0..target.0 {
let mut row = Vec::with_capacity(target.1);
for j in 0..target.1 {
let (li, lj) = project_index((i, j), shape_l);
let lv = arr
.get(li)
.and_then(|r| r.get(lj))
.cloned()
.unwrap_or(LiteralValue::Empty);
row.push(match f(lv, v.clone()) {
Ok(vv) => vv,
Err(e) => LiteralValue::Error(e),
});
}
out.push(row);
}
Ok(LiteralValue::Array(out))
}
(v, Array(arr)) => {
let shape_l = (1usize, 1usize);
let shape_r = (arr.len(), arr.first().map(|r| r.len()).unwrap_or(0));
let target = match broadcast_shape(&[shape_l, shape_r]) {
Ok(s) => s,
Err(e) => return Ok(LiteralValue::Error(e)),
};
let mut out = Vec::with_capacity(target.0);
for i in 0..target.0 {
let mut row = Vec::with_capacity(target.1);
for j in 0..target.1 {
let (ri, rj) = project_index((i, j), shape_r);
let rv = arr
.get(ri)
.and_then(|r| r.get(rj))
.cloned()
.unwrap_or(LiteralValue::Empty);
row.push(match f(v.clone(), rv) {
Ok(vv) => vv,
Err(e) => LiteralValue::Error(e),
});
}
out.push(row);
}
Ok(LiteralValue::Array(out))
}
(l, r) => f(l, r),
}
}
fn coerce_number(&self, v: &LiteralValue) -> Result<f64, ExcelError> {
coercion::to_number_lenient(v)
}
fn coerce_text(&self, v: &LiteralValue) -> String {
coercion::to_text_invariant(v)
}
fn compare(
&self,
op: &str,
left: LiteralValue,
right: LiteralValue,
) -> Result<LiteralValue, ExcelError> {
use LiteralValue::*;
if matches!(left, Error(_)) {
return Ok(left);
}
if matches!(right, Error(_)) {
return Ok(right);
}
match (left, right) {
(Array(l), Array(r)) => self.combine_arrays(l, r, |a, b| self.compare(op, a, b)),
(Array(arr), v) => self.broadcast_apply(Array(arr), v, |a, b| self.compare(op, a, b)),
(v, Array(arr)) => self.broadcast_apply(v, Array(arr), |a, b| self.compare(op, a, b)),
(l, r) => {
let res = match (l, r) {
(Number(a), Number(b)) => self.cmp_f64(a, b, op),
(Int(a), Number(b)) => self.cmp_f64(a as f64, b, op),
(Number(a), Int(b)) => self.cmp_f64(a, b as f64, op),
(Boolean(a), Boolean(b)) => {
self.cmp_f64(if a { 1.0 } else { 0.0 }, if b { 1.0 } else { 0.0 }, op)
}
(Text(a), Text(b)) => self.cmp_text(&a, &b, op),
(a, b) => {
let an = crate::coercion::to_number_lenient_with_locale(
&a,
&self.context.locale(),
)
.ok();
let bn = crate::coercion::to_number_lenient_with_locale(
&b,
&self.context.locale(),
)
.ok();
if let (Some(a), Some(b)) = (an, bn) {
self.cmp_f64(a, b, op)
} else {
self.cmp_text(
&crate::coercion::to_text_invariant(&a),
&crate::coercion::to_text_invariant(&b),
op,
)
}
}
};
Ok(LiteralValue::Boolean(res))
}
}
}
fn cmp_f64(&self, a: f64, b: f64, op: &str) -> bool {
match op {
"=" => a == b,
"<>" => a != b,
">" => a > b,
"<" => a < b,
">=" => a >= b,
"<=" => a <= b,
_ => unreachable!(),
}
}
fn cmp_text(&self, a: &str, b: &str, op: &str) -> bool {
let loc = self.context.locale();
let (a, b) = (loc.fold_case_invariant(a), loc.fold_case_invariant(b));
self.cmp_f64(
a.cmp(&b) as i32 as f64,
0.0,
match op {
"=" => "=",
"<>" => "<>",
">" => ">",
"<" => "<",
">=" => ">=",
"<=" => "<=",
_ => unreachable!(),
},
)
}
}
fn relocate_reference_for_offset(
reference: &ReferenceType,
row_delta: i64,
col_delta: i64,
) -> Result<ReferenceType, ExcelError> {
match reference {
ReferenceType::Cell {
sheet,
row,
col,
row_abs,
col_abs,
} => Ok(ReferenceType::Cell {
sheet: sheet.clone(),
row: shift_axis_for_offset(*row, row_delta, *row_abs)?,
col: shift_axis_for_offset(*col, col_delta, *col_abs)?,
row_abs: *row_abs,
col_abs: *col_abs,
}),
ReferenceType::Range {
sheet,
start_row,
start_col,
end_row,
end_col,
start_row_abs,
start_col_abs,
end_row_abs,
end_col_abs,
} => Ok(ReferenceType::Range {
sheet: sheet.clone(),
start_row: shift_optional_axis_for_offset(*start_row, row_delta, *start_row_abs)?,
start_col: shift_optional_axis_for_offset(*start_col, col_delta, *start_col_abs)?,
end_row: shift_optional_axis_for_offset(*end_row, row_delta, *end_row_abs)?,
end_col: shift_optional_axis_for_offset(*end_col, col_delta, *end_col_abs)?,
start_row_abs: *start_row_abs,
start_col_abs: *start_col_abs,
end_row_abs: *end_row_abs,
end_col_abs: *end_col_abs,
}),
ReferenceType::NamedRange(name) => Ok(ReferenceType::NamedRange(name.clone())),
ReferenceType::Table(_)
| ReferenceType::Cell3D { .. }
| ReferenceType::Range3D { .. }
| ReferenceType::External(_) => Err(unsupported_reference_relocation_error()),
}
}
fn shift_optional_axis_for_offset(
value: Option<u32>,
delta: i64,
is_absolute: bool,
) -> Result<Option<u32>, ExcelError> {
value
.map(|value| shift_axis_for_offset(value, delta, is_absolute))
.transpose()
}
fn shift_axis_for_offset(value: u32, delta: i64, is_absolute: bool) -> Result<u32, ExcelError> {
if is_absolute {
return Ok(value);
}
let shifted = i64::from(value) + delta;
if shifted < 1 || shifted > i64::from(u32::MAX) {
return Err(unsupported_reference_relocation_error());
}
Ok(shifted as u32)
}
fn unsupported_reference_relocation_error() -> ExcelError {
ExcelError::new(ExcelErrorKind::Ref)
.with_message("Unsupported reference relocation for FormulaPlane span evaluation")
}