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