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