1use crate::{
2 CellRef,
3 broadcast::{broadcast_shape, project_index},
4 coercion,
5 traits::{ArgumentHandle, DefaultFunctionContext, EvaluationContext},
6};
7use formualizer_common::{ExcelError, ExcelErrorKind, LiteralValue};
8use formualizer_parse::parser::{ASTNode, ASTNodeType, ReferenceType};
9use rustc_hash::FxHashMap;
10use std::{borrow::Cow, sync::Arc};
11
12use crate::engine::arena::ast::SheetKey;
13use crate::engine::arena::{AstNodeData, AstNodeId, CompactRefType, DataStore};
14use crate::engine::sheet_registry::SheetRegistry;
15use crate::engine::used_extent::{
16 ExtentPolicy, OpenRangeBounds, resolve_used_extent_with_fallback,
17};
18use crate::formula_plane::template_canonical::LiteralSlotId;
19
20pub(crate) fn probe_range_dimensions<C: EvaluationContext + ?Sized>(
21 context: &C,
22 current_sheet: &str,
23 reference: &ReferenceType,
24) -> Option<(u32, u32)> {
25 match reference {
26 ReferenceType::Range {
27 sheet,
28 start_row,
29 start_col,
30 end_row,
31 end_col,
32 ..
33 } => {
34 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
35 let extent = resolve_used_extent_with_fallback(
36 OpenRangeBounds {
37 start_row: *start_row,
38 start_column: *start_col,
39 end_row: *end_row,
40 end_column: *end_col,
41 },
42 ExtentPolicy::EvaluationCompat {
43 fallback_row: None,
44 fallback_column: None,
45 },
46 || context.sheet_bounds(sheet_name).map(|bounds| bounds.0),
47 || context.sheet_bounds(sheet_name).map(|bounds| bounds.1),
48 |first, last| context.used_rows_for_columns(sheet_name, first, last),
49 |first, last| context.used_cols_for_rows(sheet_name, first, last),
50 );
51 let Some(extent) = extent else {
52 return Some((0, 0));
53 };
54 Some((
55 extent.end_row - extent.start_row + 1,
56 extent.end_column - extent.start_column + 1,
57 ))
58 }
59 ReferenceType::Cell { .. } => Some((1, 1)),
60 _ => None,
61 }
62}
63
64#[derive(Clone)]
65pub enum LocalBinding {
66 Value(LiteralValue),
67 Callable(Arc<dyn crate::traits::CustomCallable>),
68}
69
70#[derive(Clone, Default)]
71pub struct LocalEnv {
72 head: Option<Arc<EnvFrame>>,
73}
74
75#[derive(Clone)]
76struct EnvFrame {
77 parent: Option<Arc<EnvFrame>>,
78 bindings: FxHashMap<String, LocalBinding>,
79}
80
81impl LocalEnv {
82 #[inline(always)]
83 pub fn is_empty(&self) -> bool {
84 self.head.is_none()
85 }
86
87 fn norm(name: &str) -> String {
88 name.to_ascii_uppercase()
89 }
90
91 pub fn lookup(&self, name: &str) -> Option<LocalBinding> {
92 self.head.as_ref()?;
93 let key = Self::norm(name);
94 let mut cur = self.head.as_ref().cloned();
95 while let Some(frame) = cur {
96 if let Some(v) = frame.bindings.get(&key) {
97 return Some(v.clone());
98 }
99 cur = frame.parent.clone();
100 }
101 None
102 }
103
104 pub fn with_binding(&self, name: &str, value: LocalBinding) -> Self {
105 let mut bindings = FxHashMap::default();
106 bindings.insert(Self::norm(name), value);
107 Self {
108 head: Some(Arc::new(EnvFrame {
109 parent: self.head.clone(),
110 bindings,
111 })),
112 }
113 }
114}
115
116#[derive(Clone, Copy)]
117pub(crate) struct InterpreterParameterBindings<'a> {
118 pub(crate) literal_slots_by_node: &'a FxHashMap<AstNodeId, LiteralSlotId>,
119 pub(crate) literal_values: &'a [LiteralValue],
120}
121
122pub struct Interpreter<'a> {
123 pub context: &'a dyn EvaluationContext,
124 current_sheet: &'a str,
125 current_cell: Option<crate::CellRef>,
126 local_env: LocalEnv,
127 reference_row_delta: i64,
128 reference_col_delta: i64,
129 disable_ast_planner: bool,
130 parameter_bindings: Option<InterpreterParameterBindings<'a>>,
131}
132
133impl<'a> Interpreter<'a> {
134 pub fn new(context: &'a dyn EvaluationContext, current_sheet: &'a str) -> Self {
135 Self {
136 context,
137 current_sheet,
138 current_cell: None,
139 local_env: LocalEnv::default(),
140 reference_row_delta: 0,
141 reference_col_delta: 0,
142 disable_ast_planner: false,
143 parameter_bindings: None,
144 }
145 }
146
147 pub fn new_with_cell(
148 context: &'a dyn EvaluationContext,
149 current_sheet: &'a str,
150 cell: crate::CellRef,
151 ) -> Self {
152 Self {
153 context,
154 current_sheet,
155 current_cell: Some(cell),
156 local_env: LocalEnv::default(),
157 reference_row_delta: 0,
158 reference_col_delta: 0,
159 disable_ast_planner: false,
160 parameter_bindings: None,
161 }
162 }
163
164 pub fn current_sheet(&self) -> &'a str {
165 self.current_sheet
166 }
167
168 pub fn local_env(&self) -> &LocalEnv {
169 &self.local_env
170 }
171
172 pub(crate) fn with_current_cell(&self, cell: crate::CellRef) -> Self {
173 Self {
174 context: self.context,
175 current_sheet: self.current_sheet,
176 current_cell: Some(cell),
177 local_env: self.local_env.clone(),
178 reference_row_delta: self.reference_row_delta,
179 reference_col_delta: self.reference_col_delta,
180 disable_ast_planner: self.disable_ast_planner,
181 parameter_bindings: self.parameter_bindings,
182 }
183 }
184
185 pub fn with_local_env(&self, env: LocalEnv) -> Self {
186 Self {
187 context: self.context,
188 current_sheet: self.current_sheet,
189 current_cell: self.current_cell,
190 local_env: env,
191 reference_row_delta: self.reference_row_delta,
192 reference_col_delta: self.reference_col_delta,
193 disable_ast_planner: self.disable_ast_planner,
194 parameter_bindings: self.parameter_bindings,
195 }
196 }
197
198 pub(crate) fn with_parameter_bindings(
199 &self,
200 bindings: InterpreterParameterBindings<'a>,
201 ) -> Self {
202 Self {
203 context: self.context,
204 current_sheet: self.current_sheet,
205 current_cell: self.current_cell,
206 local_env: self.local_env.clone(),
207 reference_row_delta: self.reference_row_delta,
208 reference_col_delta: self.reference_col_delta,
209 disable_ast_planner: self.disable_ast_planner,
210 parameter_bindings: Some(bindings),
211 }
212 }
213
214 fn effective_reference<'r>(
215 &self,
216 reference: &'r ReferenceType,
217 ) -> Result<Cow<'r, ReferenceType>, ExcelError> {
218 if self.reference_row_delta == 0 && self.reference_col_delta == 0 {
219 return Ok(Cow::Borrowed(reference));
220 }
221
222 Ok(Cow::Owned(relocate_reference_for_offset(
223 reference,
224 self.reference_row_delta,
225 self.reference_col_delta,
226 )?))
227 }
228
229 fn resolve_local_reference(
230 &self,
231 reference: &ReferenceType,
232 ) -> Option<crate::traits::CalcValue<'a>> {
233 if self.local_env.is_empty() {
234 return None;
235 }
236 let name = match reference {
237 ReferenceType::NamedRange(name) => name,
238 _ => return None,
239 };
240 match self.local_env.lookup(name)? {
241 LocalBinding::Value(v) => Some(crate::traits::CalcValue::Scalar(v)),
242 LocalBinding::Callable(c) => Some(crate::traits::CalcValue::Callable(c)),
243 }
244 }
245
246 fn resolve_local_callable(&self, name: &str) -> Option<Arc<dyn crate::traits::CustomCallable>> {
247 if self.local_env.is_empty() {
248 return None;
249 }
250 match self.local_env.lookup(name)? {
251 LocalBinding::Callable(c) => Some(c),
252 LocalBinding::Value(_) => None,
253 }
254 }
255
256 pub fn resolve_local_name(&self, name: &str) -> Option<LocalBinding> {
257 self.local_env.lookup(name)
258 }
259
260 pub fn resolve_range_view<'c>(
261 &'c self,
262 reference: &ReferenceType,
263 current_sheet: &str,
264 ) -> Result<crate::engine::range_view::RangeView<'c>, ExcelError> {
265 self.context.resolve_range_view(reference, current_sheet)
266 }
267
268 pub fn evaluate_ast_as_reference(&self, node: &ASTNode) -> Result<ReferenceType, ExcelError> {
273 match &node.node_type {
274 ASTNodeType::Reference { reference, .. } => {
275 self.reference_for_current_offset(reference)
276 }
277 ASTNodeType::Function { name, args } => {
278 if let Some(fun) = self.context.get_function("", name) {
279 let handles: Vec<ArgumentHandle> =
281 args.iter().map(|n| ArgumentHandle::new(n, self)).collect();
282 let fctx = DefaultFunctionContext::new_with_sheet(
283 self.context,
284 None,
285 self.current_sheet,
286 );
287 if let Some(res) = fun.eval_reference(&handles, &fctx) {
288 res
289 } else {
290 Err(ExcelError::new(ExcelErrorKind::Ref)
291 .with_message("Function does not return a reference"))
292 }
293 } else {
294 Err(ExcelError::new(ExcelErrorKind::Name)
295 .with_message(format!("Unknown function: {name}")))
296 }
297 }
298 ASTNodeType::BinaryOp { op, left, right } if op == ":" => {
299 let lref = self.evaluate_ast_as_reference(left)?;
300 let rref = self.evaluate_ast_as_reference(right)?;
301 crate::reference::combine_references(&lref, &rref)
302 }
303 ASTNodeType::Array(_)
304 | ASTNodeType::UnaryOp { .. }
305 | ASTNodeType::BinaryOp { .. }
306 | ASTNodeType::Call { .. }
307 | ASTNodeType::Literal(_)
308 | ASTNodeType::Omitted => Err(ExcelError::new(ExcelErrorKind::Ref)
309 .with_message("Expression cannot be used as a reference")),
310 }
311 }
312
313 pub(crate) fn try_evaluate_ast_as_reference(
314 &self,
315 node: &ASTNode,
316 ) -> Option<Result<ReferenceType, ExcelError>> {
317 let ASTNodeType::Function { name, args } = &node.node_type else {
318 return Some(self.evaluate_ast_as_reference(node));
319 };
320 let fun = match self.context.get_function("", name) {
321 Some(fun) => fun,
322 None => {
323 return Some(Err(ExcelError::new(ExcelErrorKind::Name)
324 .with_message(format!("Unknown function: {name}"))));
325 }
326 };
327 let handles: Vec<ArgumentHandle> = args
328 .iter()
329 .map(|arg| ArgumentHandle::new(arg, self))
330 .collect();
331 let fctx = DefaultFunctionContext::new_with_sheet(self.context, None, self.current_sheet);
332 fun.eval_reference(&handles, &fctx)
333 }
334
335 pub(crate) fn evaluate_arena_ast_as_reference(
336 &self,
337 node_id: AstNodeId,
338 data_store: &DataStore,
339 sheet_registry: &SheetRegistry,
340 ) -> Result<ReferenceType, ExcelError> {
341 let node = data_store.get_node(node_id).ok_or_else(|| {
342 ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
343 })?;
344
345 match node {
346 AstNodeData::Reference { ref_type, .. } => {
347 let reference =
348 data_store.reconstruct_reference_type_for_eval(ref_type, sheet_registry);
349 self.reference_for_current_offset(&reference)
350 }
351 AstNodeData::Function { name_id, .. } => {
352 let name = data_store.resolve_ast_string(*name_id);
353 let fun = self.context.get_function("", name).ok_or_else(|| {
354 ExcelError::new(ExcelErrorKind::Name)
355 .with_message(format!("Unknown function: {name}"))
356 })?;
357
358 let args = data_store.get_args(node_id).ok_or_else(|| {
359 ExcelError::new(ExcelErrorKind::Value).with_message("Missing function args")
360 })?;
361
362 let handles: Vec<ArgumentHandle> = args
363 .iter()
364 .copied()
365 .map(|arg_id| {
366 ArgumentHandle::new_arena(arg_id, self, data_store, sheet_registry)
367 })
368 .collect();
369
370 let fctx =
371 DefaultFunctionContext::new_with_sheet(self.context, None, self.current_sheet);
372
373 fun.eval_reference(&handles, &fctx).ok_or_else(|| {
374 ExcelError::new(ExcelErrorKind::Ref)
375 .with_message("Function does not return a reference")
376 })?
377 }
378 AstNodeData::BinaryOp {
379 op_id,
380 left_id,
381 right_id,
382 } => {
383 let op = data_store.resolve_ast_string(*op_id);
384 if op != ":" {
385 return Err(ExcelError::new(ExcelErrorKind::Ref)
386 .with_message("Expression cannot be used as a reference"));
387 }
388 let lref =
389 self.evaluate_arena_ast_as_reference(*left_id, data_store, sheet_registry)?;
390 let rref =
391 self.evaluate_arena_ast_as_reference(*right_id, data_store, sheet_registry)?;
392 crate::reference::combine_references(&lref, &rref)
393 }
394 _ => Err(ExcelError::new(ExcelErrorKind::Ref)
395 .with_message("Expression cannot be used as a reference")),
396 }
397 }
398
399 pub(crate) fn try_evaluate_arena_ast_as_reference(
400 &self,
401 node_id: AstNodeId,
402 data_store: &DataStore,
403 sheet_registry: &SheetRegistry,
404 ) -> Option<Result<ReferenceType, ExcelError>> {
405 let node = match data_store.get_node(node_id) {
406 Some(node) => node,
407 None => {
408 return Some(Err(
409 ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
410 ));
411 }
412 };
413 let AstNodeData::Function { name_id, .. } = node else {
414 return Some(self.evaluate_arena_ast_as_reference(node_id, data_store, sheet_registry));
415 };
416 let name = data_store.resolve_ast_string(*name_id);
417 let fun = match self.context.get_function("", name) {
418 Some(fun) => fun,
419 None => {
420 return Some(Err(ExcelError::new(ExcelErrorKind::Name)
421 .with_message(format!("Unknown function: {name}"))));
422 }
423 };
424 let args = match data_store.get_args(node_id) {
425 Some(args) => args,
426 None => {
427 return Some(Err(
428 ExcelError::new(ExcelErrorKind::Value).with_message("Missing function args")
429 ));
430 }
431 };
432 let handles: Vec<ArgumentHandle> = args
433 .iter()
434 .copied()
435 .map(|arg_id| ArgumentHandle::new_arena(arg_id, self, data_store, sheet_registry))
436 .collect();
437 let fctx = DefaultFunctionContext::new_with_sheet(self.context, None, self.current_sheet);
438 fun.eval_reference(&handles, &fctx)
439 }
440
441 pub fn evaluate_ast(&self, node: &ASTNode) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
443 self.evaluate_ast_uncached(node)
444 }
445
446 pub(crate) fn evaluate_ast_with_offset(
447 &self,
448 node: &ASTNode,
449 row_delta: i64,
450 col_delta: i64,
451 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
452 let offset = Self {
453 context: self.context,
454 current_sheet: self.current_sheet,
455 current_cell: self.current_cell,
456 local_env: self.local_env.clone(),
457 reference_row_delta: row_delta,
458 reference_col_delta: col_delta,
459 disable_ast_planner: true,
460 parameter_bindings: self.parameter_bindings,
461 };
462 offset.evaluate_ast_uncached(node)
463 }
464
465 pub(crate) fn reference_for_current_offset(
466 &self,
467 reference: &ReferenceType,
468 ) -> Result<ReferenceType, ExcelError> {
469 self.effective_reference(reference)
470 .map(|reference| reference.into_owned())
471 }
472
473 pub(crate) fn evaluate_arena_ast_with_offset(
474 &self,
475 node_id: AstNodeId,
476 row_delta: i64,
477 col_delta: i64,
478 data_store: &DataStore,
479 sheet_registry: &SheetRegistry,
480 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
481 let offset = Self {
482 context: self.context,
483 current_sheet: self.current_sheet,
484 current_cell: self.current_cell,
485 local_env: self.local_env.clone(),
486 reference_row_delta: row_delta,
487 reference_col_delta: col_delta,
488 disable_ast_planner: true,
489 parameter_bindings: self.parameter_bindings,
490 };
491 offset.evaluate_arena_ast(node_id, data_store, sheet_registry)
492 }
493
494 fn annotate_cell_value(
495 &self,
496 sheet: Option<&str>,
497 row: u32,
498 col: u32,
499 value: LiteralValue,
500 ) -> crate::traits::CalcValue<'a> {
501 match self
502 .context
503 .resolve_cell_format(sheet, row, col, self.current_sheet)
504 {
505 Some(format) => crate::traits::CalcValue::AnnotatedScalar(value, format),
506 None => crate::traits::CalcValue::Scalar(value),
507 }
508 }
509
510 fn binary_format(
511 &self,
512 op: char,
513 left: Option<crate::format::FormatId>,
514 right: Option<crate::format::FormatId>,
515 ) -> Option<crate::format::FormatId> {
516 use formualizer_common::numfmt::FormatClass;
517 let class =
518 |id: Option<crate::format::FormatId>| id.and_then(|id| self.context.format_class(id));
519 let left = class(left);
520 let right = class(right);
521 let is_plain = |class: &Option<FormatClass>| {
522 matches!(
523 class,
524 None | Some(FormatClass::General | FormatClass::Number { .. })
525 )
526 };
527 match (op, left.as_ref(), right.as_ref()) {
532 ('+', Some(FormatClass::Date), Some(FormatClass::Time))
533 | ('+', Some(FormatClass::Time), Some(FormatClass::Date)) => {
534 Some(crate::format::FormatId::DATETIME)
535 }
536 ('+', Some(FormatClass::Date), Some(FormatClass::Percent { .. }))
537 | ('+', Some(FormatClass::Percent { .. }), Some(FormatClass::Date)) => {
538 Some(crate::format::FormatId::DATE)
539 }
540 ('+' | '-', Some(FormatClass::Date), r) if is_plain(&r.cloned()) => {
541 Some(crate::format::FormatId::DATE)
542 }
543 ('+', l, Some(FormatClass::Date)) if is_plain(&l.cloned()) => {
544 Some(crate::format::FormatId::DATE)
545 }
546 ('+' | '-', Some(FormatClass::Time), r) if is_plain(&r.cloned()) => {
547 Some(crate::format::FormatId::TIME)
548 }
549 ('+', l, Some(FormatClass::Time)) if is_plain(&l.cloned()) => {
550 Some(crate::format::FormatId::TIME)
551 }
552 ('+' | '-', Some(FormatClass::DateTime), r) if is_plain(&r.cloned()) => {
553 Some(crate::format::FormatId::DATETIME)
554 }
555 ('+', l, Some(FormatClass::DateTime)) if is_plain(&l.cloned()) => {
556 Some(crate::format::FormatId::DATETIME)
557 }
558 ('+' | '-', Some(FormatClass::Duration), r) if is_plain(&r.cloned()) => {
559 Some(crate::format::FormatId::DURATION)
560 }
561 ('+', l, Some(FormatClass::Duration)) if is_plain(&l.cloned()) => {
562 Some(crate::format::FormatId::DURATION)
563 }
564 _ => None,
565 }
566 }
567
568 fn annotate_numeric_result(
569 &self,
570 value: LiteralValue,
571 format: Option<crate::format::FormatId>,
572 ) -> crate::traits::CalcValue<'a> {
573 match (value, format) {
574 (value @ LiteralValue::Number(_), Some(format)) => {
575 crate::traits::CalcValue::AnnotatedScalar(value, format)
576 }
577 (value, _) => crate::traits::CalcValue::Scalar(value),
578 }
579 }
580
581 pub(crate) fn evaluate_arena_ast(
582 &self,
583 node_id: AstNodeId,
584 data_store: &DataStore,
585 sheet_registry: &SheetRegistry,
586 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
587 let node = data_store.get_node(node_id).ok_or_else(|| {
588 ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
589 })?;
590
591 match node {
592 AstNodeData::Literal(vref) => {
593 if let Some(bindings) = self.parameter_bindings
594 && let Some(slot_id) = bindings.literal_slots_by_node.get(&node_id)
595 && let Some(value) = bindings.literal_values.get(slot_id.0 as usize)
596 {
597 return Ok(crate::traits::CalcValue::Scalar(value.clone()));
598 }
599 Ok(crate::traits::CalcValue::Scalar(
600 data_store.retrieve_value(*vref),
601 ))
602 }
603 AstNodeData::Omitted => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0))),
604 AstNodeData::Reference { ref_type, .. } => {
605 if self.local_env.is_empty()
606 && let CompactRefType::Cell {
607 sheet,
608 row,
609 col,
610 row_abs,
611 col_abs,
612 } = ref_type
613 && *row > 0
614 && *col > 0
615 {
616 let sheet_name = match sheet {
617 Some(SheetKey::Id(id)) => Some(sheet_registry.name(*id)),
618 Some(SheetKey::Name(name_id)) => {
619 Some(data_store.resolve_ast_string(*name_id))
620 }
621 None => None,
622 };
623 let row = shift_axis_for_offset(*row, self.reference_row_delta, *row_abs)?;
624 let col = shift_axis_for_offset(*col, self.reference_col_delta, *col_abs)?;
625 let value = self.context.resolve_cell_reference_value(
626 sheet_name,
627 row,
628 col,
629 self.current_sheet,
630 )?;
631 Ok(self.annotate_cell_value(sheet_name, row, col, value))
632 } else {
633 let reference =
634 data_store.reconstruct_reference_type_for_eval(ref_type, sheet_registry);
635 let reference = self.effective_reference(&reference)?;
636 if let Some(local) = self.resolve_local_reference(&reference) {
637 return Ok(local);
638 }
639 self.eval_reference_to_calc(&reference)
640 }
641 }
642 AstNodeData::UnaryOp { op_id, expr_id } => {
643 let expr = self.evaluate_arena_ast(*expr_id, data_store, sheet_registry)?;
644
645 let op = data_store.resolve_ast_string(*op_id);
646 if op == "@" {
647 if let Some(AstNodeData::Reference { ref_type, .. }) =
650 data_store.get_node(*expr_id)
651 {
652 let reference = data_store
653 .reconstruct_reference_type_for_eval(ref_type, sheet_registry);
654 let v = self.implicit_intersection_from_reference(&reference);
655 return Ok(crate::traits::CalcValue::Scalar(v));
656 }
657
658 let v = self.eval_implicit_intersection_calc(expr);
659 return Ok(crate::traits::CalcValue::Scalar(v));
660 }
661 let v = expr.into_literal();
663 match v {
664 LiteralValue::Array(arr) => self
665 .map_array(arr, |cell| self.eval_unary_scalar(op, cell))
666 .map(crate::traits::CalcValue::Scalar),
667 other => self
668 .eval_unary_scalar(op, other)
669 .map(crate::traits::CalcValue::Scalar),
670 }
671 }
672 AstNodeData::BinaryOp {
673 op_id,
674 left_id,
675 right_id,
676 } => {
677 let op = data_store.resolve_ast_string(*op_id);
678 if op == ":" {
679 let lref =
680 self.evaluate_arena_ast_as_reference(*left_id, data_store, sheet_registry)?;
681 let rref = self.evaluate_arena_ast_as_reference(
682 *right_id,
683 data_store,
684 sheet_registry,
685 )?;
686 return match crate::reference::combine_references(&lref, &rref) {
687 Ok(_r) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
688 ExcelError::new(ExcelErrorKind::Ref).with_message(
689 "Reference produced by ':' cannot be used directly as a value",
690 ),
691 ))),
692 Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
693 };
694 }
695
696 let left_calc = self.evaluate_arena_ast(*left_id, data_store, sheet_registry)?;
697 let left_format = left_calc.format_id();
698 let left = left_calc.into_literal();
699 let right_calc = self.evaluate_arena_ast(*right_id, data_store, sheet_registry)?;
700 let right_format = right_calc.format_id();
701 let right = right_calc.into_literal();
702
703 if matches!(op, "=" | "<>" | ">" | "<" | ">=" | "<=") {
704 return self
705 .compare(op, left, right)
706 .map(crate::traits::CalcValue::Scalar);
707 }
708
709 match op {
710 "+" => self.numeric_binary(left, right, |a, b| a + b).map(|value| {
711 self.annotate_numeric_result(
712 value,
713 self.binary_format('+', left_format, right_format),
714 )
715 }),
716 "-" => self.numeric_binary(left, right, |a, b| a - b).map(|value| {
717 self.annotate_numeric_result(
718 value,
719 self.binary_format('-', left_format, right_format),
720 )
721 }),
722 "*" => self
723 .numeric_binary(left, right, |a, b| a * b)
724 .map(crate::traits::CalcValue::Scalar),
725 "/" => self
726 .divide(left, right)
727 .map(crate::traits::CalcValue::Scalar),
728 "^" => self
729 .power(left, right)
730 .map(crate::traits::CalcValue::Scalar),
731 "&" => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
732 format!(
733 "{}{}",
734 crate::coercion::to_text_invariant(&left),
735 crate::coercion::to_text_invariant(&right)
736 ),
737 ))),
738 _ => Err(ExcelError::new(ExcelErrorKind::NImpl)
739 .with_message(format!("Binary op '{op}'"))),
740 }
741 }
742 AstNodeData::Array { .. } => {
743 let (rows, cols, elements) =
744 data_store.get_array_elems(node_id).ok_or_else(|| {
745 ExcelError::new(ExcelErrorKind::Value).with_message("Invalid array")
746 })?;
747
748 let rows_usize = rows as usize;
749 let cols_usize = cols as usize;
750 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows_usize);
751 for r in 0..rows_usize {
752 let mut row = Vec::with_capacity(cols_usize);
753 for c in 0..cols_usize {
754 let idx = r * cols_usize + c;
755 if let Some(&elem_id) = elements.get(idx) {
756 row.push(
757 self.evaluate_arena_ast(elem_id, data_store, sheet_registry)?
758 .into_literal(),
759 );
760 }
761 }
762 out.push(row);
763 }
764
765 Ok(crate::traits::CalcValue::Range(
766 crate::engine::range_view::RangeView::from_owned_rows(
767 out,
768 self.context.date_system(),
769 ),
770 ))
771 }
772 AstNodeData::Function { name_id, .. } => {
773 let name = data_store.resolve_ast_string(*name_id);
774 let args = data_store.get_args(node_id).ok_or_else(|| {
775 ExcelError::new(ExcelErrorKind::Value).with_message("Missing function args")
776 })?;
777
778 if let Some(fun) = self.context.get_function("", name) {
779 let handles: Vec<ArgumentHandle> = args
780 .iter()
781 .copied()
782 .map(|arg_id| {
783 ArgumentHandle::new_arena(arg_id, self, data_store, sheet_registry)
784 })
785 .collect();
786
787 let fctx = DefaultFunctionContext::new_with_sheet(
788 self.context,
789 self.current_cell,
790 self.current_sheet,
791 );
792
793 return fun.dispatch(&handles, &fctx);
794 }
795
796 if let Some(callable) = self.resolve_local_callable(name) {
797 let mut eval_args = Vec::with_capacity(args.len());
798 for arg_id in args {
799 eval_args.push(
800 self.evaluate_arena_ast(*arg_id, data_store, sheet_registry)?
801 .into_literal(),
802 );
803 }
804 return callable.invoke(self, &eval_args);
805 }
806
807 Err(ExcelError::new(ExcelErrorKind::Name)
808 .with_message(format!("Unknown function: {name}")))
809 }
810 }
811 }
812
813 fn evaluate_ast_uncached(
814 &self,
815 node: &ASTNode,
816 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
817 if self.disable_ast_planner {
818 return self.eval_tree_uncached(node);
819 }
820
821 let current_sheet = self.current_sheet.to_string();
825 let range_probe = |reference: &ReferenceType| {
826 probe_range_dimensions(self.context, ¤t_sheet, reference)
827 };
828 let fn_lookup = |ns: &str, name: &str| self.context.get_function(ns, name);
829
830 let mut planner = crate::planner::Planner::new(crate::planner::PlanConfig::default())
831 .with_range_probe(&range_probe)
832 .with_function_lookup(&fn_lookup);
833 let plan = planner.plan(node);
834 self.eval_with_plan(node, &plan.root)
835 }
836
837 fn eval_tree_uncached(
838 &self,
839 node: &ASTNode,
840 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
841 match &node.node_type {
842 ASTNodeType::Literal(v) => Ok(crate::traits::CalcValue::Scalar(v.clone())),
843 ASTNodeType::Omitted => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0))),
844 ASTNodeType::Reference { reference, .. } => self.eval_ast_reference_to_calc(reference),
845 ASTNodeType::UnaryOp { op, expr } => self
846 .eval_unary(op, expr)
847 .map(crate::traits::CalcValue::Scalar),
848 ASTNodeType::BinaryOp { op, left, right } => self.eval_binary(op, left, right),
849 ASTNodeType::Function { name, args } => self.eval_function_to_calc(name, args),
850 ASTNodeType::Call { .. } => Err(ExcelError::new(ExcelErrorKind::NImpl)
851 .with_message("Immediate-invocation calls are not yet supported")),
852 ASTNodeType::Array(rows) => self.eval_array_literal_to_calc(rows),
853 }
854 }
855
856 fn eval_with_plan(
857 &self,
858 node: &ASTNode,
859 plan_node: &crate::planner::PlanNode,
860 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
861 match &node.node_type {
862 ASTNodeType::Literal(v) => Ok(crate::traits::CalcValue::Scalar(v.clone())),
863 ASTNodeType::Omitted => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0))),
864 ASTNodeType::Reference { reference, .. } => self.eval_ast_reference_to_calc(reference),
865 ASTNodeType::UnaryOp { op, expr } => {
866 self.eval_unary(op, expr)
869 .map(crate::traits::CalcValue::Scalar)
870 }
871 ASTNodeType::BinaryOp { op, left, right } => self.eval_binary(op, left, right),
872 ASTNodeType::Function { name, args } => {
873 let strategy = plan_node.strategy;
874 if let Some(fun) = self.context.get_function("", name) {
875 use crate::function::FnCaps;
876 use crate::planner::ExecStrategy;
877 let caps = fun.caps();
878
879 if caps.contains(FnCaps::SHORT_CIRCUIT) || caps.contains(FnCaps::VOLATILE) {
881 return self.eval_function_to_calc(name, args);
882 }
883
884 if matches!(strategy, ExecStrategy::ArgParallel)
888 && caps.contains(FnCaps::PARALLEL_ARGS)
889 {
890 for arg in args {
892 match &arg.node_type {
893 ASTNodeType::Reference { reference, .. } => {
894 if let Ok(reference) = self.effective_reference(reference) {
895 let _ = self
896 .context
897 .resolve_range_view(&reference, self.current_sheet);
898 }
899 }
900 _ => {
901 let _ = self.evaluate_ast(arg);
902 }
903 }
904 }
905 return self.eval_function_to_calc(name, args);
906 }
907
908 return self.eval_function_to_calc(name, args);
910 }
911 self.eval_function_to_calc(name, args)
912 }
913 ASTNodeType::Call { .. } => Err(ExcelError::new(ExcelErrorKind::NImpl)
914 .with_message("Immediate-invocation calls are not yet supported")),
915 ASTNodeType::Array(rows) => self.eval_array_literal_to_calc(rows),
916 }
917 }
918
919 fn eval_ast_reference_to_calc(
921 &self,
922 reference: &ReferenceType,
923 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
924 if !self.local_env.is_empty() {
925 let reference = self.effective_reference(reference)?;
926 if let Some(local) = self.resolve_local_reference(&reference) {
927 return Ok(local);
928 }
929 return self.eval_reference_to_calc(&reference);
930 }
931
932 if let ReferenceType::Cell {
933 sheet,
934 row,
935 col,
936 row_abs,
937 col_abs,
938 } = reference
939 {
940 let row = shift_axis_for_offset(*row, self.reference_row_delta, *row_abs)?;
941 let col = shift_axis_for_offset(*col, self.reference_col_delta, *col_abs)?;
942 let value = self.context.resolve_cell_reference_value(
943 sheet.as_deref(),
944 row,
945 col,
946 self.current_sheet,
947 )?;
948 return Ok(self.annotate_cell_value(sheet.as_deref(), row, col, value));
949 }
950
951 let reference = self.effective_reference(reference)?;
952 self.eval_reference_to_calc(&reference)
953 }
954
955 fn eval_reference_to_calc(
956 &self,
957 reference: &ReferenceType,
958 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
959 if let ReferenceType::Cell {
960 sheet, row, col, ..
961 } = reference
962 {
963 let value = self.context.resolve_cell_reference_value(
964 sheet.as_deref(),
965 *row,
966 *col,
967 self.current_sheet,
968 )?;
969 return Ok(self.annotate_cell_value(sheet.as_deref(), *row, *col, value));
970 }
971
972 let view = self
973 .context
974 .resolve_range_view(reference, self.current_sheet)?
975 .with_cancel_token(self.context.cancellation_token());
976 Ok(crate::traits::CalcValue::Range(view))
977 }
978
979 fn eval_reference(&self, reference: &ReferenceType) -> Result<LiteralValue, ExcelError> {
980 self.eval_reference_to_calc(reference)
981 .map(|cv| cv.into_literal())
982 }
983
984 fn eval_unary(&self, op: &str, expr: &ASTNode) -> Result<LiteralValue, ExcelError> {
986 if op == "@" {
987 if let ASTNodeType::Reference { reference, .. } = &expr.node_type {
988 let reference = self.effective_reference(reference)?;
989 return Ok(self.implicit_intersection_from_reference(&reference));
990 }
991
992 let cv = self.evaluate_ast(expr)?;
993 return Ok(self.eval_implicit_intersection_calc(cv));
994 }
995
996 let v = self.evaluate_ast(expr)?.into_literal();
997 match v {
998 LiteralValue::Array(arr) => {
999 self.map_array(arr, |cell| self.eval_unary_scalar(op, cell))
1000 }
1001 other => self.eval_unary_scalar(op, other),
1002 }
1003 }
1004
1005 fn eval_unary_scalar(&self, op: &str, v: LiteralValue) -> Result<LiteralValue, ExcelError> {
1006 match op {
1007 "+" => Ok(v),
1012 "-" => self.apply_number_unary(v, |n| -n),
1013 "%" => self.apply_number_unary(v, |n| n / 100.0),
1014 _ => {
1015 Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!("Unary op '{op}'")))
1016 }
1017 }
1018 }
1019
1020 fn eval_implicit_intersection_calc(&self, cv: crate::traits::CalcValue<'a>) -> LiteralValue {
1021 let (cur_r0, cur_c0) = match self.current_cell {
1022 Some(cell) => (cell.coord.row() as usize, cell.coord.col() as usize),
1023 None => (0usize, 0usize),
1024 };
1025
1026 match cv {
1027 crate::traits::CalcValue::Scalar(v)
1028 | crate::traits::CalcValue::AnnotatedScalar(v, _) => match v {
1029 LiteralValue::Array(arr) => {
1030 if arr.is_empty() || arr.first().map(|r| r.is_empty()).unwrap_or(true) {
1031 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1032 }
1033 arr[0][0].clone()
1034 }
1035 other => other,
1036 },
1037 crate::traits::CalcValue::Range(rv) => {
1038 if rv.is_empty() {
1039 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1040 }
1041
1042 if rv.sheet_name() == "__tmp" {
1047 return rv.get_cell(0, 0);
1048 }
1049
1050 if let Some(v) = rv.as_1x1() {
1051 return v;
1052 }
1053
1054 let (rows, cols) = rv.dims();
1055 let sr = rv.start_row();
1056 let sc = rv.start_col();
1057 let er = rv.end_row();
1058 let ec = rv.end_col();
1059
1060 if cols == 1 {
1065 if cur_r0 < sr || cur_r0 > er {
1066 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1067 }
1068 let rel_r = cur_r0 - sr;
1069 return rv.get_cell(rel_r, 0);
1070 }
1071
1072 if rows == 1 {
1073 if cur_c0 < sc || cur_c0 > ec {
1074 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1075 }
1076 let rel_c = cur_c0 - sc;
1077 return rv.get_cell(0, rel_c);
1078 }
1079
1080 if cur_r0 < sr || cur_r0 > er || cur_c0 < sc || cur_c0 > ec {
1081 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1082 }
1083 let rel_r = cur_r0 - sr;
1084 let rel_c = cur_c0 - sc;
1085 rv.get_cell(rel_r, rel_c)
1086 }
1087 crate::traits::CalcValue::Callable(_) => LiteralValue::Error(
1088 ExcelError::new(ExcelErrorKind::Calc).with_message("LAMBDA value must be invoked"),
1089 ),
1090 }
1091 }
1092
1093 fn implicit_intersection_from_reference(&self, reference: &ReferenceType) -> LiteralValue {
1094 let (cur_r1, cur_c1) = match self.current_cell {
1095 Some(cell) => (
1096 cell.coord.row().saturating_add(1),
1097 cell.coord.col().saturating_add(1),
1098 ),
1099 None => (1u32, 1u32),
1100 };
1101
1102 match reference {
1103 ReferenceType::Cell {
1104 sheet, row, col, ..
1105 } => {
1106 let sheet_name = sheet.as_deref().unwrap_or(self.current_sheet);
1107 match self
1108 .context
1109 .resolve_cell_reference(Some(sheet_name), *row, *col)
1110 {
1111 Ok(v) => v,
1112 Err(e) => LiteralValue::Error(e),
1113 }
1114 }
1115 ReferenceType::Range {
1116 sheet,
1117 start_row,
1118 start_col,
1119 end_row,
1120 end_col,
1121 ..
1122 } => {
1123 let sheet_name = sheet.as_deref().unwrap_or(self.current_sheet);
1124
1125 let (sr, sc, er, ec) = match (start_row, start_col, end_row, end_col) {
1126 (Some(sr), Some(sc), Some(er), Some(ec)) => (*sr, *sc, *er, *ec),
1127 _ => {
1128 let cv = match self.eval_reference_to_calc(reference) {
1131 Ok(cv) => cv,
1132 Err(e) => return LiteralValue::Error(e),
1133 };
1134 return self.eval_implicit_intersection_calc(cv);
1135 }
1136 };
1137
1138 let (mut sr, mut er) = (sr, er);
1140 let (mut sc, mut ec) = (sc, ec);
1141 if sr > er {
1142 std::mem::swap(&mut sr, &mut er);
1143 }
1144 if sc > ec {
1145 std::mem::swap(&mut sc, &mut ec);
1146 }
1147
1148 let pick = if sc == ec {
1149 if cur_r1 < sr || cur_r1 > er {
1151 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1152 }
1153 (cur_r1, sc)
1154 } else if sr == er {
1155 if cur_c1 < sc || cur_c1 > ec {
1157 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1158 }
1159 (sr, cur_c1)
1160 } else {
1161 if cur_r1 < sr || cur_r1 > er || cur_c1 < sc || cur_c1 > ec {
1163 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1164 }
1165 (cur_r1, cur_c1)
1166 };
1167
1168 match self
1169 .context
1170 .resolve_cell_reference(Some(sheet_name), pick.0, pick.1)
1171 {
1172 Ok(v) => v,
1173 Err(e) => LiteralValue::Error(e),
1174 }
1175 }
1176 other => {
1178 let cv = match self.eval_reference_to_calc(other) {
1179 Ok(cv) => cv,
1180 Err(e) => return LiteralValue::Error(e),
1181 };
1182 self.eval_implicit_intersection_calc(cv)
1183 }
1184 }
1185 }
1186
1187 fn apply_number_unary<F>(&self, v: LiteralValue, f: F) -> Result<LiteralValue, ExcelError>
1188 where
1189 F: Fn(f64) -> f64,
1190 {
1191 match crate::coercion::to_arithmetic_number_with_locale(
1192 &v,
1193 &self.context.locale(),
1194 self.context.date_system(),
1195 ) {
1196 Ok(n) => match crate::coercion::sanitize_numeric(f(n)) {
1197 Ok(n2) => Ok(LiteralValue::Number(n2)),
1198 Err(e) => Ok(LiteralValue::Error(e)),
1199 },
1200 Err(e) => Ok(LiteralValue::Error(e)),
1201 }
1202 }
1203
1204 fn eval_binary(
1206 &self,
1207 op: &str,
1208 left_node: &ASTNode,
1209 right_node: &ASTNode,
1210 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1211 let left_calc = self.evaluate_ast(left_node)?;
1212 let left_format = left_calc.format_id();
1213 let left = left_calc.into_literal();
1214 let right_calc = self.evaluate_ast(right_node)?;
1215 let right_format = right_calc.format_id();
1216 let right = right_calc.into_literal();
1217 if matches!(op, "=" | "<>" | ">" | "<" | ">=" | "<=") {
1218 return self
1219 .compare(op, left, right)
1220 .map(crate::traits::CalcValue::Scalar);
1221 }
1222 match op {
1223 "+" => self.numeric_binary(left, right, |a, b| a + b).map(|value| {
1224 self.annotate_numeric_result(
1225 value,
1226 self.binary_format('+', left_format, right_format),
1227 )
1228 }),
1229 "-" => self.numeric_binary(left, right, |a, b| a - b).map(|value| {
1230 self.annotate_numeric_result(
1231 value,
1232 self.binary_format('-', left_format, right_format),
1233 )
1234 }),
1235 "*" => self
1236 .numeric_binary(left, right, |a, b| a * b)
1237 .map(crate::traits::CalcValue::Scalar),
1238 "/" => self
1239 .divide(left, right)
1240 .map(crate::traits::CalcValue::Scalar),
1241 "^" => self
1242 .power(left, right)
1243 .map(crate::traits::CalcValue::Scalar),
1244 "&" => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Text(
1245 format!(
1246 "{}{}",
1247 crate::coercion::to_text_invariant(&left),
1248 crate::coercion::to_text_invariant(&right)
1249 ),
1250 ))),
1251 ":" => {
1252 let left_ref = self.evaluate_ast_as_reference(left_node)?;
1253 let right_ref = self.evaluate_ast_as_reference(right_node)?;
1254 match crate::reference::combine_references(&left_ref, &right_ref) {
1255 Ok(_) => Err(ExcelError::new(ExcelErrorKind::Ref).with_message(
1256 "Reference produced by ':' cannot be used directly as a value",
1257 )),
1258 Err(error) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(error))),
1259 }
1260 }
1261 _ => {
1262 Err(ExcelError::new(ExcelErrorKind::NImpl)
1263 .with_message(format!("Binary op '{op}'")))
1264 }
1265 }
1266 }
1267
1268 fn eval_function_to_calc(
1270 &self,
1271 name: &str,
1272 args: &[ASTNode],
1273 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1274 if let Some(fun) = self.context.get_function("", name) {
1275 let handles: Vec<ArgumentHandle> =
1276 args.iter().map(|n| ArgumentHandle::new(n, self)).collect();
1277 let fctx = DefaultFunctionContext::new_with_sheet(
1279 self.context,
1280 self.current_cell,
1281 self.current_sheet,
1282 );
1283 return fun.dispatch(&handles, &fctx);
1284 }
1285
1286 if let Some(callable) = self.resolve_local_callable(name) {
1287 let mut eval_args = Vec::with_capacity(args.len());
1288 for arg in args {
1289 eval_args.push(self.evaluate_ast(arg)?.into_literal());
1290 }
1291 return callable.invoke(self, &eval_args);
1292 }
1293
1294 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1296 ExcelError::new(ExcelErrorKind::Name).with_message(format!("Unknown function: {name}")),
1297 )))
1298 }
1299
1300 fn eval_function(&self, name: &str, args: &[ASTNode]) -> Result<LiteralValue, ExcelError> {
1301 self.eval_function_to_calc(name, args)
1302 .map(|cv| cv.into_literal())
1303 }
1304
1305 pub fn function_context(&self, cell_ref: Option<&CellRef>) -> DefaultFunctionContext<'_> {
1306 DefaultFunctionContext::new_with_sheet(self.context, cell_ref.cloned(), self.current_sheet)
1307 }
1308
1309 fn eval_array_literal_to_calc(
1311 &self,
1312 rows: &[Vec<ASTNode>],
1313 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1314 let mut out = Vec::with_capacity(rows.len());
1315 for row in rows {
1316 let mut r = Vec::with_capacity(row.len());
1317 for cell in row {
1318 r.push(self.evaluate_ast(cell)?.into_literal());
1319 }
1320 out.push(r);
1321 }
1322 Ok(crate::traits::CalcValue::Range(
1323 crate::engine::range_view::RangeView::from_owned_rows(out, self.context.date_system()),
1324 ))
1325 }
1326
1327 fn eval_array_literal(&self, rows: &[Vec<ASTNode>]) -> Result<LiteralValue, ExcelError> {
1328 self.eval_array_literal_to_calc(rows)
1329 .map(|cv| cv.into_literal())
1330 }
1331
1332 fn numeric_binary<F>(
1333 &self,
1334 left: LiteralValue,
1335 right: LiteralValue,
1336 f: F,
1337 ) -> Result<LiteralValue, ExcelError>
1338 where
1339 F: Fn(f64, f64) -> f64 + Copy,
1340 {
1341 self.broadcast_apply(left, right, |l, r| {
1342 let a = crate::coercion::to_arithmetic_number_with_locale(
1343 &l,
1344 &self.context.locale(),
1345 self.context.date_system(),
1346 );
1347 let b = crate::coercion::to_arithmetic_number_with_locale(
1348 &r,
1349 &self.context.locale(),
1350 self.context.date_system(),
1351 );
1352 match (a, b) {
1353 (Ok(a), Ok(b)) => match crate::coercion::sanitize_numeric(f(a, b)) {
1354 Ok(n2) => Ok(LiteralValue::Number(n2)),
1355 Err(e) => Ok(LiteralValue::Error(e)),
1356 },
1357 (Err(e), _) | (_, Err(e)) => Ok(LiteralValue::Error(e)),
1358 }
1359 })
1360 }
1361
1362 fn divide(&self, left: LiteralValue, right: LiteralValue) -> Result<LiteralValue, ExcelError> {
1363 self.broadcast_apply(left, right, |l, r| {
1364 let ln = crate::coercion::to_arithmetic_number_with_locale(
1365 &l,
1366 &self.context.locale(),
1367 self.context.date_system(),
1368 );
1369 let rn = crate::coercion::to_arithmetic_number_with_locale(
1370 &r,
1371 &self.context.locale(),
1372 self.context.date_system(),
1373 );
1374 let (a, b) = match (ln, rn) {
1375 (Ok(a), Ok(b)) => (a, b),
1376 (Err(e), _) | (_, Err(e)) => return Ok(LiteralValue::Error(e)),
1377 };
1378 if b == 0.0 {
1379 return Ok(LiteralValue::Error(ExcelError::from_error_string(
1380 "#DIV/0!",
1381 )));
1382 }
1383 match crate::coercion::sanitize_numeric(a / b) {
1384 Ok(n) => Ok(LiteralValue::Number(n)),
1385 Err(e) => Ok(LiteralValue::Error(e)),
1386 }
1387 })
1388 }
1389
1390 fn power(&self, left: LiteralValue, right: LiteralValue) -> Result<LiteralValue, ExcelError> {
1391 self.broadcast_apply(left, right, |l, r| {
1392 let ln = crate::coercion::to_arithmetic_number_with_locale(
1393 &l,
1394 &self.context.locale(),
1395 self.context.date_system(),
1396 );
1397 let rn = crate::coercion::to_arithmetic_number_with_locale(
1398 &r,
1399 &self.context.locale(),
1400 self.context.date_system(),
1401 );
1402 let (a, b) = match (ln, rn) {
1403 (Ok(a), Ok(b)) => (a, b),
1404 (Err(e), _) | (_, Err(e)) => return Ok(LiteralValue::Error(e)),
1405 };
1406 if a < 0.0 && b.fract() != 0.0 {
1408 return Ok(LiteralValue::Error(ExcelError::new_num()));
1409 }
1410 match crate::coercion::sanitize_numeric(a.powf(b)) {
1411 Ok(n) => Ok(LiteralValue::Number(n)),
1412 Err(e) => Ok(LiteralValue::Error(e)),
1413 }
1414 })
1415 }
1416
1417 fn map_array<F>(&self, arr: Vec<Vec<LiteralValue>>, f: F) -> Result<LiteralValue, ExcelError>
1418 where
1419 F: Fn(LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
1420 {
1421 let mut out = Vec::with_capacity(arr.len());
1422 for row in arr {
1423 let mut new_row = Vec::with_capacity(row.len());
1424 for cell in row {
1425 new_row.push(match f(cell) {
1426 Ok(v) => v,
1427 Err(e) => LiteralValue::Error(e),
1428 });
1429 }
1430 out.push(new_row);
1431 }
1432 Ok(LiteralValue::Array(out))
1433 }
1434
1435 fn combine_arrays<F>(
1436 &self,
1437 l: Vec<Vec<LiteralValue>>,
1438 r: Vec<Vec<LiteralValue>>,
1439 f: F,
1440 ) -> Result<LiteralValue, ExcelError>
1441 where
1442 F: Fn(LiteralValue, LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
1443 {
1444 let l_shape = (l.len(), l.first().map(|r| r.len()).unwrap_or(0));
1446 let r_shape = (r.len(), r.first().map(|r| r.len()).unwrap_or(0));
1447 let target = match broadcast_shape(&[l_shape, r_shape]) {
1448 Ok(s) => s,
1449 Err(e) => return Ok(LiteralValue::Error(e)),
1450 };
1451
1452 let mut out = Vec::with_capacity(target.0);
1453 for i in 0..target.0 {
1454 let mut row = Vec::with_capacity(target.1);
1455 for j in 0..target.1 {
1456 let (li, lj) = project_index((i, j), l_shape);
1457 let (ri, rj) = project_index((i, j), r_shape);
1458 let lv = l
1459 .get(li)
1460 .and_then(|r| r.get(lj))
1461 .cloned()
1462 .unwrap_or(LiteralValue::Empty);
1463 let rv = r
1464 .get(ri)
1465 .and_then(|r| r.get(rj))
1466 .cloned()
1467 .unwrap_or(LiteralValue::Empty);
1468 row.push(match f(lv, rv) {
1469 Ok(v) => v,
1470 Err(e) => LiteralValue::Error(e),
1471 });
1472 }
1473 out.push(row);
1474 }
1475 Ok(LiteralValue::Array(out))
1476 }
1477
1478 fn broadcast_apply<F>(
1479 &self,
1480 left: LiteralValue,
1481 right: LiteralValue,
1482 f: F,
1483 ) -> Result<LiteralValue, ExcelError>
1484 where
1485 F: Fn(LiteralValue, LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
1486 {
1487 use LiteralValue::*;
1488 match (left, right) {
1489 (Array(l), Array(r)) => self.combine_arrays(l, r, f),
1490 (Array(arr), v) => {
1491 let shape_l = (arr.len(), arr.first().map(|r| r.len()).unwrap_or(0));
1492 let shape_r = (1usize, 1usize);
1493 let target = match broadcast_shape(&[shape_l, shape_r]) {
1494 Ok(s) => s,
1495 Err(e) => return Ok(LiteralValue::Error(e)),
1496 };
1497 let mut out = Vec::with_capacity(target.0);
1498 for i in 0..target.0 {
1499 let mut row = Vec::with_capacity(target.1);
1500 for j in 0..target.1 {
1501 let (li, lj) = project_index((i, j), shape_l);
1502 let lv = arr
1503 .get(li)
1504 .and_then(|r| r.get(lj))
1505 .cloned()
1506 .unwrap_or(LiteralValue::Empty);
1507 row.push(match f(lv, v.clone()) {
1508 Ok(vv) => vv,
1509 Err(e) => LiteralValue::Error(e),
1510 });
1511 }
1512 out.push(row);
1513 }
1514 Ok(LiteralValue::Array(out))
1515 }
1516 (v, Array(arr)) => {
1517 let shape_l = (1usize, 1usize);
1518 let shape_r = (arr.len(), arr.first().map(|r| r.len()).unwrap_or(0));
1519 let target = match broadcast_shape(&[shape_l, shape_r]) {
1520 Ok(s) => s,
1521 Err(e) => return Ok(LiteralValue::Error(e)),
1522 };
1523 let mut out = Vec::with_capacity(target.0);
1524 for i in 0..target.0 {
1525 let mut row = Vec::with_capacity(target.1);
1526 for j in 0..target.1 {
1527 let (ri, rj) = project_index((i, j), shape_r);
1528 let rv = arr
1529 .get(ri)
1530 .and_then(|r| r.get(rj))
1531 .cloned()
1532 .unwrap_or(LiteralValue::Empty);
1533 row.push(match f(v.clone(), rv) {
1534 Ok(vv) => vv,
1535 Err(e) => LiteralValue::Error(e),
1536 });
1537 }
1538 out.push(row);
1539 }
1540 Ok(LiteralValue::Array(out))
1541 }
1542 (l, r) => f(l, r),
1543 }
1544 }
1545
1546 fn coerce_number(&self, v: &LiteralValue) -> Result<f64, ExcelError> {
1548 coercion::to_number_lenient(v)
1549 }
1550
1551 fn coerce_text(&self, v: &LiteralValue) -> String {
1552 coercion::to_text_invariant(v)
1553 }
1554
1555 fn compare(
1557 &self,
1558 op: &str,
1559 left: LiteralValue,
1560 right: LiteralValue,
1561 ) -> Result<LiteralValue, ExcelError> {
1562 use LiteralValue::*;
1563 if matches!(left, Error(_)) {
1564 return Ok(left);
1565 }
1566 if matches!(right, Error(_)) {
1567 return Ok(right);
1568 }
1569
1570 match (left, right) {
1572 (Array(l), Array(r)) => self.combine_arrays(l, r, |a, b| self.compare(op, a, b)),
1573 (Array(arr), v) => self.broadcast_apply(Array(arr), v, |a, b| self.compare(op, a, b)),
1574 (v, Array(arr)) => self.broadcast_apply(v, Array(arr), |a, b| self.compare(op, a, b)),
1575 (l, r) => {
1576 let res = match (l, r) {
1577 (Number(a), Number(b)) => self.cmp_f64(a, b, op),
1578 (Int(a), Number(b)) => self.cmp_f64(a as f64, b, op),
1579 (Number(a), Int(b)) => self.cmp_f64(a, b as f64, op),
1580 (Boolean(a), Boolean(b)) => {
1581 self.cmp_f64(if a { 1.0 } else { 0.0 }, if b { 1.0 } else { 0.0 }, op)
1582 }
1583 (Text(a), Text(b)) => self.cmp_text(&a, &b, op),
1584 (a, b) => {
1585 let an = crate::coercion::to_number_lenient_with_locale(
1587 &a,
1588 &self.context.locale(),
1589 )
1590 .ok();
1591 let bn = crate::coercion::to_number_lenient_with_locale(
1592 &b,
1593 &self.context.locale(),
1594 )
1595 .ok();
1596 if let (Some(a), Some(b)) = (an, bn) {
1597 self.cmp_f64(a, b, op)
1598 } else {
1599 self.cmp_text(
1600 &crate::coercion::to_text_invariant(&a),
1601 &crate::coercion::to_text_invariant(&b),
1602 op,
1603 )
1604 }
1605 }
1606 };
1607 Ok(LiteralValue::Boolean(res))
1608 }
1609 }
1610 }
1611
1612 fn cmp_f64(&self, a: f64, b: f64, op: &str) -> bool {
1613 match op {
1614 "=" => a == b,
1615 "<>" => a != b,
1616 ">" => a > b,
1617 "<" => a < b,
1618 ">=" => a >= b,
1619 "<=" => a <= b,
1620 _ => unreachable!(),
1621 }
1622 }
1623 fn cmp_text(&self, a: &str, b: &str, op: &str) -> bool {
1624 let loc = self.context.locale();
1625 let (a, b) = (loc.fold_case_invariant(a), loc.fold_case_invariant(b));
1626 self.cmp_f64(
1627 a.cmp(&b) as i32 as f64,
1628 0.0,
1629 match op {
1630 "=" => "=",
1631 "<>" => "<>",
1632 ">" => ">",
1633 "<" => "<",
1634 ">=" => ">=",
1635 "<=" => "<=",
1636 _ => unreachable!(),
1637 },
1638 )
1639 }
1640}
1641
1642fn relocate_reference_for_offset(
1643 reference: &ReferenceType,
1644 row_delta: i64,
1645 col_delta: i64,
1646) -> Result<ReferenceType, ExcelError> {
1647 match reference {
1648 ReferenceType::Cell {
1649 sheet,
1650 row,
1651 col,
1652 row_abs,
1653 col_abs,
1654 } => Ok(ReferenceType::Cell {
1655 sheet: sheet.clone(),
1656 row: shift_axis_for_offset(*row, row_delta, *row_abs)?,
1657 col: shift_axis_for_offset(*col, col_delta, *col_abs)?,
1658 row_abs: *row_abs,
1659 col_abs: *col_abs,
1660 }),
1661 ReferenceType::Range {
1662 sheet,
1663 start_row,
1664 start_col,
1665 end_row,
1666 end_col,
1667 start_row_abs,
1668 start_col_abs,
1669 end_row_abs,
1670 end_col_abs,
1671 } => Ok(ReferenceType::Range {
1672 sheet: sheet.clone(),
1673 start_row: shift_optional_axis_for_offset(*start_row, row_delta, *start_row_abs)?,
1674 start_col: shift_optional_axis_for_offset(*start_col, col_delta, *start_col_abs)?,
1675 end_row: shift_optional_axis_for_offset(*end_row, row_delta, *end_row_abs)?,
1676 end_col: shift_optional_axis_for_offset(*end_col, col_delta, *end_col_abs)?,
1677 start_row_abs: *start_row_abs,
1678 start_col_abs: *start_col_abs,
1679 end_row_abs: *end_row_abs,
1680 end_col_abs: *end_col_abs,
1681 }),
1682 ReferenceType::NamedRange(name) => Ok(ReferenceType::NamedRange(name.clone())),
1685 ReferenceType::Table(_)
1686 | ReferenceType::Cell3D { .. }
1687 | ReferenceType::Range3D { .. }
1688 | ReferenceType::External(_) => Err(unsupported_reference_relocation_error()),
1689 }
1690}
1691
1692fn shift_optional_axis_for_offset(
1693 value: Option<u32>,
1694 delta: i64,
1695 is_absolute: bool,
1696) -> Result<Option<u32>, ExcelError> {
1697 value
1698 .map(|value| shift_axis_for_offset(value, delta, is_absolute))
1699 .transpose()
1700}
1701
1702fn shift_axis_for_offset(value: u32, delta: i64, is_absolute: bool) -> Result<u32, ExcelError> {
1703 if is_absolute {
1704 return Ok(value);
1705 }
1706 let shifted = i64::from(value) + delta;
1707 if shifted < 1 || shifted > i64::from(u32::MAX) {
1708 return Err(unsupported_reference_relocation_error());
1709 }
1710 Ok(shifted as u32)
1711}
1712
1713fn unsupported_reference_relocation_error() -> ExcelError {
1714 ExcelError::new(ExcelErrorKind::Ref)
1715 .with_message("Unsupported reference relocation for FormulaPlane span evaluation")
1716}
1717
1718#[cfg(test)]
1719mod format_algebra_tests {
1720 use super::*;
1721 use crate::engine::{EvalConfig, eval::Engine};
1722 use crate::format::FormatId;
1723 use crate::test_workbook::TestWorkbook;
1724
1725 #[test]
1726 fn temporal_binary_format_algebra_pins_positive_and_negative_cases() {
1727 let engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
1728 let interpreter = Interpreter::new(&engine, "Sheet1");
1729
1730 assert_eq!(
1731 interpreter.binary_format('+', Some(FormatId::DATE), Some(FormatId::TIME)),
1732 Some(FormatId::DATETIME)
1733 );
1734 assert_eq!(
1735 interpreter.binary_format('+', Some(FormatId::DATE), Some(FormatId(9))),
1736 Some(FormatId::DATE),
1737 "Date + Percent follows the measured temporal-wins rule"
1738 );
1739 assert_eq!(
1740 interpreter.binary_format('-', Some(FormatId::DATE), Some(FormatId::DATE)),
1741 None,
1742 "Date - Date is an unformatted duration in days"
1743 );
1744 assert_eq!(
1745 interpreter.binary_format('+', Some(FormatId::DATE), Some(FormatId(49))),
1746 None,
1747 "Date + Text must not acquire a temporal annotation"
1748 );
1749 for (left, right) in [
1750 (FormatId::DATE, FormatId::DATE),
1751 (FormatId::DURATION, FormatId::DATE),
1752 (FormatId::DATE, FormatId(5)),
1753 (FormatId::DATETIME, FormatId::TIME),
1754 ] {
1755 assert_eq!(
1756 interpreter.binary_format('+', Some(left), Some(right)),
1757 None
1758 );
1759 }
1760 }
1761}