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 formualizer_parse::{BinaryOperator, UnaryOperator};
10use rustc_hash::FxHashMap;
11use std::{borrow::Cow, sync::Arc};
12
13use crate::engine::arena::ast::{CALL_NODE_NAME, SheetKey};
14use crate::engine::arena::{AstNodeData, AstNodeId, CompactRefType, DataStore};
15use crate::engine::sheet_registry::SheetRegistry;
16use crate::engine::template::canonical::LiteralSlotId;
17use crate::engine::used_extent::{
18 ExtentPolicy, OpenRangeBounds, resolve_used_extent_with_fallback,
19};
20use crate::function_registry::ProviderReporting;
21
22fn spill_operand_error() -> ExcelError {
24 ExcelError::new(ExcelErrorKind::Ref)
25 .with_message("Spill reference operand must be a single cell or a name")
26}
27
28fn call_expression_error() -> ExcelError {
31 ExcelError::new(ExcelErrorKind::NImpl)
32 .with_message("Immediate-invocation calls are not yet supported")
33}
34
35pub(crate) fn probe_range_dimensions<C: EvaluationContext + ?Sized>(
36 context: &C,
37 current_sheet: &str,
38 reference: &ReferenceType,
39) -> Option<(u32, u32)> {
40 match reference {
41 ReferenceType::Range {
42 sheet,
43 start_row,
44 start_col,
45 end_row,
46 end_col,
47 ..
48 } => {
49 let sheet_name = sheet.as_deref().unwrap_or(current_sheet);
50 let extent = resolve_used_extent_with_fallback(
51 OpenRangeBounds {
52 start_row: *start_row,
53 start_column: *start_col,
54 end_row: *end_row,
55 end_column: *end_col,
56 },
57 ExtentPolicy::EvaluationCompat {
58 fallback_row: None,
59 fallback_column: None,
60 },
61 || context.sheet_bounds(sheet_name).map(|bounds| bounds.0),
62 || context.sheet_bounds(sheet_name).map(|bounds| bounds.1),
63 |first, last| context.used_rows_for_columns(sheet_name, first, last),
64 |first, last| context.used_cols_for_rows(sheet_name, first, last),
65 );
66 let Some(extent) = extent else {
67 return Some((0, 0));
68 };
69 Some((
70 extent.end_row - extent.start_row + 1,
71 extent.end_column - extent.start_column + 1,
72 ))
73 }
74 ReferenceType::Cell { .. } => Some((1, 1)),
75 _ => None,
76 }
77}
78
79#[derive(Clone)]
80pub enum LocalBinding {
81 Value(LiteralValue),
82 ValueWithReference {
88 value: LiteralValue,
89 reference: ReferenceType,
90 },
91 Callable(Arc<dyn crate::traits::CustomCallable>),
92}
93
94#[derive(Clone, Default)]
95pub struct LocalEnv {
96 head: Option<Arc<EnvFrame>>,
97}
98
99#[derive(Clone)]
100struct EnvFrame {
101 parent: Option<Arc<EnvFrame>>,
102 bindings: FxHashMap<String, LocalBinding>,
103}
104
105impl LocalEnv {
106 #[inline(always)]
107 pub fn is_empty(&self) -> bool {
108 self.head.is_none()
109 }
110
111 fn norm(name: &str) -> String {
112 name.to_ascii_uppercase()
113 }
114
115 pub(crate) fn contains_name(&self, name: &str) -> bool {
116 if self.head.is_none() {
117 return false;
118 }
119 let key = Self::norm(name);
120 let mut current = self.head.as_deref();
121 while let Some(frame) = current {
122 if frame.bindings.contains_key(&key) {
123 return true;
124 }
125 current = frame.parent.as_deref();
126 }
127 false
128 }
129
130 pub fn lookup(&self, name: &str) -> Option<LocalBinding> {
131 self.head.as_ref()?;
132 let key = Self::norm(name);
133 let mut cur = self.head.as_ref().cloned();
134 while let Some(frame) = cur {
135 if let Some(v) = frame.bindings.get(&key) {
136 return Some(v.clone());
137 }
138 cur = frame.parent.clone();
139 }
140 None
141 }
142
143 pub fn with_binding(&self, name: &str, value: LocalBinding) -> Self {
144 let mut bindings = FxHashMap::default();
145 bindings.insert(Self::norm(name), value);
146 Self {
147 head: Some(Arc::new(EnvFrame {
148 parent: self.head.clone(),
149 bindings,
150 })),
151 }
152 }
153}
154
155#[derive(Clone, Copy)]
156pub(crate) struct InterpreterParameterBindings<'a> {
157 pub(crate) literal_slots_by_node: &'a FxHashMap<AstNodeId, LiteralSlotId>,
158 pub(crate) literal_values: &'a [LiteralValue],
159 pub(crate) invariant_values: Option<&'a InvariantValues>,
162}
163
164pub(crate) type InvariantValues =
166 FxHashMap<AstNodeId, (LiteralValue, Option<crate::format::FormatId>)>;
167
168pub(crate) trait ArenaCellResolver {
170 fn resolve_arena_range(
171 &self,
172 reference: &CompactRefType,
173 current: crate::SheetId,
174 row_delta: i64,
175 col_delta: i64,
176 ) -> Result<crate::engine::range_view::RangeView<'_>, ExcelError>;
177
178 fn resolve_arena_cell(
179 &self,
180 sheet: Option<SheetKey>,
181 current: crate::SheetId,
182 row: u32,
183 col: u32,
184 ) -> Result<(LiteralValue, Option<crate::format::FormatId>), ExcelError>;
185}
186
187type ResolvedCallable = (
188 Option<std::sync::Arc<dyn crate::function::Function>>,
189 Option<crate::function::FnCaps>,
190 ProviderReporting,
191);
192
193#[derive(Default)]
195pub(crate) struct ResolvedFunctions {
196 callback_state: Option<std::sync::Arc<crate::external_callbacks::CallbackState>>,
197 functions: Vec<Option<std::sync::Arc<dyn crate::function::Function>>>,
198 slots: rustc_hash::FxHashMap<crate::engine::arena::StringId, usize>,
199 revision: Option<(u64, Option<u64>, u64)>,
200 ast_revision: Option<(u64, Option<u64>, u64, usize, u64)>,
201 ast_functions: rustc_hash::FxHashMap<Box<str>, ResolvedCallable>,
202 capabilities: Vec<Option<crate::function::FnCaps>>,
203 reporting: Vec<ProviderReporting>,
204}
205
206impl ResolvedFunctions {
207 pub(crate) fn refresh(
208 &mut self,
209 store: &DataStore,
210 provider: &dyn crate::traits::FunctionProvider,
211 ) {
212 self.callback_state = crate::external_callbacks::active();
213 let revision = (
214 crate::function_registry::semantic_epoch_lock_free(),
215 provider.planning_semantic_revision(),
216 store.resolution_revision(),
217 );
218 if self.revision != Some(revision) || revision.1.is_none() {
219 self.functions.clear();
220 self.capabilities.clear();
221 self.reporting.clear();
222 self.slots.clear();
223 self.ast_revision = None;
224 }
225 for id in store.function_names() {
228 if self.slots.contains_key(&id) {
229 continue;
230 }
231 let name = store.resolve_ast_string(id);
232 let function = if name == CALL_NODE_NAME {
233 None
234 } else {
235 provider.get_function("", name)
236 };
237 let caps = function.as_ref().map(|function| {
238 crate::external_callbacks::metadata(
239 function.as_ref(),
240 crate::function::FnCaps::empty,
241 || function.caps(),
242 )
243 });
244 let reporting = function
245 .as_ref()
246 .map_or(ProviderReporting::Unverified, |function| {
247 crate::function_registry::operation_reporting_contract("", name, function)
248 });
249 self.reporting.push(reporting);
250 self.slots.insert(id, self.functions.len());
251 self.functions.push(function);
252 self.capabilities.push(caps);
253 }
254 self.revision = Some(revision);
256 }
257
258 pub(crate) fn is_current(
264 &self,
265 store: &DataStore,
266 provider: &dyn crate::traits::FunctionProvider,
267 ) -> bool {
268 let Some((epoch, Some(provider_revision), arena_revision)) = self.revision else {
269 return false;
270 };
271 epoch == crate::function_registry::semantic_epoch_lock_free()
272 && provider.planning_semantic_revision() == Some(provider_revision)
273 && store.resolution_revision() == arena_revision
274 && store
275 .function_names()
276 .all(|id| self.slots.contains_key(&id))
277 }
278
279 pub(crate) fn refresh_ast_functions<'n>(
280 &mut self,
281 store: &DataStore,
282 provider: &dyn crate::traits::FunctionProvider,
283 named: impl Iterator<Item = &'n ASTNode>,
284 symbol_revision: u64,
285 ) {
286 let Some((epoch, provider_revision, arena_revision)) = self.revision else {
287 return;
288 };
289 let revision = (
290 epoch,
291 provider_revision,
292 arena_revision,
293 self.functions.len(),
294 symbol_revision,
295 );
296 if self.ast_revision == Some(revision) {
297 return;
298 }
299 fn collect(node: &ASTNode, names: &mut std::collections::BTreeSet<String>) {
300 match &node.node_type {
301 ASTNodeType::Function { name, args } => {
302 names.insert(name.clone());
303 for arg in args {
304 collect(arg, names);
305 }
306 }
307 ASTNodeType::UnaryOp { expr, .. } => collect(expr, names),
308 ASTNodeType::BinaryOp { left, right, .. } => {
309 collect(left, names);
310 collect(right, names);
311 }
312 ASTNodeType::Array(rows) => {
313 for node in rows.iter().flatten() {
314 collect(node, names);
315 }
316 }
317 _ => {}
318 }
319 }
320 let mut names = std::collections::BTreeSet::new();
321 let definitions_changed = self
322 .ast_revision
323 .is_none_or(|previous| previous.4 != symbol_revision);
324 if definitions_changed {
325 for node in named {
326 collect(node, &mut names);
327 }
328 self.ast_functions.clear();
329 }
330 for (id, slot) in &self.slots {
331 let name = store.resolve_ast_string(*id);
332 if self.ast_functions.contains_key(name) {
333 continue;
334 }
335 let function = self.functions.get(*slot).cloned().flatten();
336 let caps = self.capabilities.get(*slot).copied().flatten();
337 self.ast_functions
338 .insert(name.into(), (function, caps, self.reporting[*slot]));
339 }
340 for name in names {
341 self.ast_functions
342 .entry(name.into_boxed_str())
343 .or_insert_with_key(|name| {
344 let function = provider.get_function("", name);
345 let caps = function.as_ref().map(|function| {
346 crate::external_callbacks::metadata(
347 function.as_ref(),
348 crate::function::FnCaps::empty,
349 || function.caps(),
350 )
351 });
352 let reporting =
353 function
354 .as_ref()
355 .map_or(ProviderReporting::Unverified, |function| {
356 crate::function_registry::operation_reporting_contract(
357 "", name, function,
358 )
359 });
360 (function, caps, reporting)
361 });
362 }
363 self.ast_revision = Some(revision);
364 }
365
366 pub(crate) fn get_named(&self, name: &str) -> Option<&dyn crate::function::Function> {
369 if crate::external_callbacks::planning_failed() {
370 return None;
371 }
372 self.ast_functions.get(name)?.0.as_deref()
373 }
374
375 pub(crate) fn get(
376 &self,
377 id: crate::engine::arena::StringId,
378 ) -> Option<&dyn crate::function::Function> {
379 if crate::external_callbacks::planning_failed() {
380 return None;
381 }
382 self.get_with_reporting(id).map(|(function, _)| function)
383 }
384
385 pub(crate) fn get_for_optimized_call(
389 &self,
390 id: crate::engine::arena::StringId,
391 evidence: bool,
392 ) -> Option<&dyn crate::function::Function> {
393 if crate::external_callbacks::planning_failed() {
394 return None;
395 }
396 let (function, reporting) = self.get_with_reporting(id)?;
397 (crate::external_callbacks::metadata_error(function, "").is_none()
398 && (!evidence || reporting == ProviderReporting::SealedBuiltin))
399 .then_some(function)
400 }
401
402 fn get_with_reporting(
403 &self,
404 id: crate::engine::arena::StringId,
405 ) -> Option<(&dyn crate::function::Function, ProviderReporting)> {
406 let slot = *self.slots.get(&id)?;
407 Some((self.functions.get(slot)?.as_deref()?, self.reporting[slot]))
408 }
409}
410
411pub(crate) enum ResolvedFunction<'a> {
412 Borrowed(&'a dyn crate::function::Function, ProviderReporting),
413 Owned(
414 std::sync::Arc<dyn crate::function::Function>,
415 ProviderReporting,
416 ),
417}
418impl ResolvedFunction<'_> {
419 pub(crate) fn reports_known_limitations(&self) -> bool {
420 match self {
421 Self::Borrowed(_, reporting) | Self::Owned(_, reporting) => {
422 *reporting != ProviderReporting::Unverified
423 }
424 }
425 }
426 fn owned(name: &str, function: std::sync::Arc<dyn crate::function::Function>) -> Self {
427 let reporting = crate::function_registry::operation_reporting_contract("", name, &function);
428 Self::Owned(function, reporting)
429 }
430}
431impl std::ops::Deref for ResolvedFunction<'_> {
432 type Target = dyn crate::function::Function;
433 fn deref(&self) -> &Self::Target {
434 match self {
435 Self::Borrowed(f, _) => *f,
436 Self::Owned(f, _) => f.as_ref(),
437 }
438 }
439}
440
441pub struct Interpreter<'a> {
442 pub context: &'a dyn EvaluationContext,
443 pub(crate) diagnostics: Option<&'a crate::operation_diagnostics::OperationDiagnosticCollector>,
444 resolved_functions: Option<&'a ResolvedFunctions>,
445 arena_cells: Option<&'a dyn ArenaCellResolver>,
446 current_sheet: &'a str,
447 current_cell: Option<crate::CellRef>,
448 local_env: LocalEnv,
449 reference_row_delta: i64,
450 reference_col_delta: i64,
451 disable_ast_planner: bool,
452 parameter_bindings: Option<InterpreterParameterBindings<'a>>,
453}
454
455impl<'a> Interpreter<'a> {
456 pub(crate) fn reference_diagnostic_scope(
457 &self,
458 ) -> Option<crate::operation_diagnostics::Scope<'a>> {
459 self.diagnostics
460 .map(|collector| collector.reference_scope(self.current_cell))
461 }
462
463 fn unavailable(&self, operation: crate::operation_diagnostics::UnavailableOperation) {
464 if let Some(collector) = self.diagnostics {
465 collector.record(self.current_cell, operation);
466 }
467 }
468
469 fn unknown_function(&self, name: &str) {
470 self.unavailable(
471 crate::operation_diagnostics::UnavailableOperation::UnknownFunction {
472 name: name.to_owned(),
473 },
474 );
475 }
476
477 fn unsupported_call(&self) -> ExcelError {
478 self.unavailable(crate::operation_diagnostics::UnavailableOperation::PostfixInvocation);
479 call_expression_error()
480 }
481
482 fn function_call<T: crate::function::PanicResult>(
484 &self,
485 name: &str,
486 run: impl FnOnce() -> T,
487 ) -> T {
488 let result = self.function_catch(name, run);
489 let failed = self
492 .resolved_functions
493 .and_then(|t| t.callback_state.as_deref())
494 .is_none_or(crate::external_callbacks::CallbackState::failed);
495 if failed {
496 crate::external_callbacks::pending().map_or(result, T::panicked)
497 } else {
498 result
499 }
500 }
501 fn function_catch<T: crate::function::PanicResult>(
502 &self,
503 name: &str,
504 run: impl FnOnce() -> T,
505 ) -> T {
506 match crate::function::catch_function_panic(name, run) {
507 Ok(result) => result,
508 Err(error) => {
509 self.unavailable(
510 crate::operation_diagnostics::UnavailableOperation::FunctionPanicked {
511 name: name.to_ascii_uppercase(),
512 },
513 );
514 T::panicked(error)
515 }
516 }
517 }
518
519 #[inline(always)]
520 fn execution_error(
521 &self,
522 function: &dyn crate::function::Function,
523 name: &str,
524 ) -> Option<ExcelError> {
525 match self
526 .resolved_functions
527 .and_then(|table| table.callback_state.as_deref())
528 {
529 Some(state) if !state.failed() => None,
530 Some(state) => state.execution_error(function, name),
531 None => crate::external_callbacks::execution_error(function, name),
532 }
533 }
534
535 pub(crate) fn diagnostic_dispatch<T: crate::function::PanicResult>(
536 &self,
537 name: &str,
538 function: &dyn crate::function::Function,
539 reporting: bool,
540 run: impl FnOnce() -> T,
541 unimplemented: impl FnOnce(&T) -> bool,
542 ) -> T {
543 if self.diagnostics.is_none()
547 && let Some(state) = self
548 .resolved_functions
549 .and_then(|table| table.callback_state.as_deref())
550 && !state.failed()
551 {
552 let value = self.function_catch(name, run);
553 return if state.failed() {
554 state
555 .execution_error(function, name)
556 .map_or(value, T::panicked)
557 } else {
558 value
559 };
560 }
561 let collector = self.diagnostics;
562 if let Some(collector) = collector {
563 collector.dispatch_started();
564 }
565 let scope = collector.map(|c| c.scope(crate::operation_diagnostics::Mode::Inherit));
566 let result = if let Some(error) = self.execution_error(function, name) {
567 let operation_name = match error.extra() {
568 formualizer_common::ExcelErrorExtra::FunctionPanicked { function, .. } => {
569 function.clone()
570 }
571 _ => name.to_ascii_uppercase(),
572 };
573 self.unavailable(
574 crate::operation_diagnostics::UnavailableOperation::FunctionPanicked {
575 name: operation_name,
576 },
577 );
578 T::panicked(error)
579 } else {
580 let value = self.function_call(name, run);
581 if let Some(error) = self.execution_error(function, name) {
582 let operation_name = match error.extra() {
583 formualizer_common::ExcelErrorExtra::FunctionPanicked { function, .. } => {
584 function.clone()
585 }
586 _ => name.to_ascii_uppercase(),
587 };
588 self.unavailable(
589 crate::operation_diagnostics::UnavailableOperation::FunctionPanicked {
590 name: operation_name,
591 },
592 );
593 T::panicked(error)
594 } else {
595 value
596 }
597 };
598 if let Some(scope) = scope {
599 if !scope.tainted() && unimplemented(&result) {
600 self.unavailable(
601 crate::operation_diagnostics::UnavailableOperation::UnimplementedFunction {
602 name: name.to_ascii_uppercase(),
603 },
604 );
605 }
606 if !reporting
607 && collector.is_some_and(|collector| collector.tracks_unverified_providers())
608 {
609 self.unavailable(
610 crate::operation_diagnostics::UnavailableOperation::UnverifiedProvider {
611 name: name.to_ascii_uppercase(),
612 },
613 );
614 }
615 }
616 result
617 }
618
619 pub fn new(context: &'a dyn EvaluationContext, current_sheet: &'a str) -> Self {
620 Self {
621 context,
622 diagnostics: context.operation_diagnostic_collector(),
623 resolved_functions: None,
624 arena_cells: None,
625 current_sheet,
626 current_cell: None,
627 local_env: LocalEnv::default(),
628 reference_row_delta: 0,
629 reference_col_delta: 0,
630 disable_ast_planner: false,
631 parameter_bindings: None,
632 }
633 }
634
635 pub fn new_with_cell(
636 context: &'a dyn EvaluationContext,
637 current_sheet: &'a str,
638 cell: crate::CellRef,
639 ) -> Self {
640 Self {
641 context,
642 diagnostics: context.operation_diagnostic_collector(),
643 resolved_functions: None,
644 arena_cells: None,
645 current_sheet,
646 current_cell: Some(cell),
647 local_env: LocalEnv::default(),
648 reference_row_delta: 0,
649 reference_col_delta: 0,
650 disable_ast_planner: false,
651 parameter_bindings: None,
652 }
653 }
654
655 pub(crate) fn with_arena_cells(mut self, resolver: &'a dyn ArenaCellResolver) -> Self {
656 self.arena_cells = Some(resolver);
657 self
658 }
659
660 pub(crate) fn with_resolved_functions(mut self, table: &'a ResolvedFunctions) -> Self {
661 self.resolved_functions = Some(table);
662 self
663 }
664
665 pub(crate) fn ast_function(&self, name: &str) -> Option<ResolvedFunction<'_>> {
666 if let Some(table) = self.resolved_functions
667 && let Some(function) = table.ast_functions.get(name)
668 {
669 return function
670 .0
671 .as_deref()
672 .map(|f| ResolvedFunction::Borrowed(f, function.2));
673 }
674 self.context
675 .get_function("", name)
676 .map(|function| ResolvedFunction::owned(name, function))
677 }
678
679 pub(crate) fn ast_function_capabilities(&self, name: &str) -> Option<crate::function::FnCaps> {
680 if let Some(table) = self.resolved_functions
681 && let Some(caps) = table.ast_functions.get(name)
682 {
683 return caps.1;
684 }
685 self.context.function_capabilities("", name)
686 }
687
688 pub(crate) fn ast_function_arc(
689 &self,
690 ns: &str,
691 name: &str,
692 ) -> Option<std::sync::Arc<dyn crate::function::Function>> {
693 if ns.is_empty()
694 && let Some(table) = self.resolved_functions
695 && let Some(function) = table.ast_functions.get(name)
696 {
697 return function.0.clone();
698 }
699 self.context.get_function(ns, name)
700 }
701
702 fn arena_function(
703 &self,
704 id: crate::engine::arena::StringId,
705 store: &DataStore,
706 ) -> Option<ResolvedFunction<'_>> {
707 if let Some(table) = self.resolved_functions {
708 table
709 .get_with_reporting(id)
710 .map(|(f, reporting)| ResolvedFunction::Borrowed(f, reporting))
711 } else {
712 let name = store.resolve_ast_string(id);
713 if name == CALL_NODE_NAME {
714 return None;
715 }
716 self.context
717 .get_function("", name)
718 .map(|function| ResolvedFunction::owned(name, function))
719 }
720 }
721
722 pub(crate) fn arena_function_capabilities(
723 &self,
724 id: crate::engine::arena::StringId,
725 store: &DataStore,
726 ) -> Option<crate::function::FnCaps> {
727 if let Some(table) = self.resolved_functions {
728 table
729 .capabilities
730 .get(*table.slots.get(&id)?)
731 .copied()
732 .flatten()
733 } else {
734 self.context
735 .function_capabilities("", store.resolve_ast_string(id))
736 }
737 }
738
739 pub fn current_sheet(&self) -> &'a str {
740 self.current_sheet
741 }
742
743 pub fn local_env(&self) -> &LocalEnv {
744 &self.local_env
745 }
746
747 pub(crate) fn with_current_cell(&self, cell: crate::CellRef) -> Self {
748 Self {
749 context: self.context,
750 diagnostics: self.diagnostics,
751 resolved_functions: self.resolved_functions,
752 arena_cells: self.arena_cells,
753 current_sheet: self.current_sheet,
754 current_cell: Some(cell),
755 local_env: self.local_env.clone(),
756 reference_row_delta: self.reference_row_delta,
757 reference_col_delta: self.reference_col_delta,
758 disable_ast_planner: self.disable_ast_planner,
759 parameter_bindings: self.parameter_bindings,
760 }
761 }
762
763 pub fn with_local_env(&self, env: LocalEnv) -> Self {
764 Self {
765 context: self.context,
766 diagnostics: self.diagnostics,
767 resolved_functions: self.resolved_functions,
768 arena_cells: self.arena_cells,
769 current_sheet: self.current_sheet,
770 current_cell: self.current_cell,
771 local_env: env,
772 reference_row_delta: self.reference_row_delta,
773 reference_col_delta: self.reference_col_delta,
774 disable_ast_planner: self.disable_ast_planner,
775 parameter_bindings: self.parameter_bindings,
776 }
777 }
778
779 pub(crate) fn with_parameter_bindings(
780 &self,
781 bindings: InterpreterParameterBindings<'a>,
782 ) -> Self {
783 Self {
784 context: self.context,
785 diagnostics: self.diagnostics,
786 resolved_functions: self.resolved_functions,
787 arena_cells: self.arena_cells,
788 current_sheet: self.current_sheet,
789 current_cell: self.current_cell,
790 local_env: self.local_env.clone(),
791 reference_row_delta: self.reference_row_delta,
792 reference_col_delta: self.reference_col_delta,
793 disable_ast_planner: self.disable_ast_planner,
794 parameter_bindings: Some(bindings),
795 }
796 }
797
798 fn effective_reference<'r>(
799 &self,
800 reference: &'r ReferenceType,
801 ) -> Result<Cow<'r, ReferenceType>, ExcelError> {
802 if self.reference_row_delta == 0 && self.reference_col_delta == 0 {
803 return Ok(Cow::Borrowed(reference));
804 }
805
806 Ok(Cow::Owned(relocate_reference_for_offset(
807 reference,
808 self.reference_row_delta,
809 self.reference_col_delta,
810 )?))
811 }
812
813 fn resolve_local_reference(
814 &self,
815 reference: &ReferenceType,
816 ) -> Option<crate::traits::CalcValue<'a>> {
817 if self.local_env.is_empty() {
818 return None;
819 }
820 let name = match reference {
821 ReferenceType::NamedRange(name) => name,
822 _ => return None,
823 };
824 match self.local_env.lookup(name)? {
825 LocalBinding::Value(v) => Some(crate::traits::CalcValue::Scalar(v)),
826 LocalBinding::ValueWithReference { value, .. } => {
827 Some(crate::traits::CalcValue::Scalar(value))
828 }
829 LocalBinding::Callable(c) => Some(crate::traits::CalcValue::Callable(c)),
830 }
831 }
832
833 fn resolve_local_callable(&self, name: &str) -> Option<Arc<dyn crate::traits::CustomCallable>> {
834 if self.local_env.is_empty() {
835 return None;
836 }
837 match self.local_env.lookup(name)? {
838 LocalBinding::Callable(c) => Some(c),
839 LocalBinding::Value(_) | LocalBinding::ValueWithReference { .. } => None,
840 }
841 }
842
843 pub(crate) fn has_local_name(&self, name: &str) -> bool {
844 self.local_env.contains_name(name)
845 }
846
847 pub fn resolve_local_name(&self, name: &str) -> Option<LocalBinding> {
848 self.local_env.lookup(name)
849 }
850
851 pub(crate) fn resolve_local_bound_reference(&self, name: &str) -> Option<ReferenceType> {
854 if self.local_env.is_empty() {
855 return None;
856 }
857 match self.local_env.lookup(name)? {
858 LocalBinding::ValueWithReference { reference, .. } => Some(reference),
859 LocalBinding::Value(_) | LocalBinding::Callable(_) => None,
860 }
861 }
862
863 pub fn resolve_range_view<'c>(
864 &'c self,
865 reference: &ReferenceType,
866 current_sheet: &str,
867 ) -> Result<crate::engine::range_view::RangeView<'c>, ExcelError> {
868 self.context
869 .resolve_range_view(reference, current_sheet)
870 .map(|view| view.with_cell_budget(self.context.materialized_array_cells()))
871 }
872
873 pub fn evaluate_ast_as_reference(&self, node: &ASTNode) -> Result<ReferenceType, ExcelError> {
878 let _root = crate::external_callbacks::root(self.current_cell);
879 if let Some(error) = crate::external_callbacks::pending() {
880 return Err(error);
881 }
882
883 match &node.node_type {
884 ASTNodeType::Reference { reference, .. } => {
885 self.reference_for_current_offset(reference)
886 }
887 ASTNodeType::Function { name, args } => {
888 if let Some(fun) = self.ast_function(name) {
889 let handles: smallvec::SmallVec<[ArgumentHandle; 4]> =
891 args.iter().map(|n| ArgumentHandle::new(n, self)).collect();
892 let fctx = DefaultFunctionContext::new_with_sheet(
893 self.context,
894 None,
895 self.current_sheet,
896 );
897 let _origin = self.reference_diagnostic_scope();
898 if let Some(res) = self.diagnostic_dispatch(
899 name,
900 &*fun,
901 fun.reports_known_limitations(),
902 || fun.eval_reference(&handles, &fctx),
903 crate::operation_diagnostics::is_unimplemented_reference,
904 ) {
905 res
906 } else {
907 Err(ExcelError::new(ExcelErrorKind::Ref)
908 .with_message("Function does not return a reference"))
909 }
910 } else {
911 self.unknown_function(name);
912 Err(ExcelError::new(ExcelErrorKind::Name)
913 .with_message(format!("Unknown function: {name}")))
914 }
915 }
916 ASTNodeType::BinaryOp { op, left, right } if *op == BinaryOperator::Range => {
917 let lref = self.evaluate_ast_as_reference(left)?;
918 let rref = self.evaluate_ast_as_reference(right)?;
919 crate::reference::combine_references(&lref, &rref)
920 }
921 ASTNodeType::UnaryOp { op, expr } if *op == UnaryOperator::Spill => {
922 self.ast_spill_reference(expr)
923 }
924 ASTNodeType::Array(_)
925 | ASTNodeType::UnaryOp { .. }
926 | ASTNodeType::BinaryOp { .. }
927 | ASTNodeType::Call { .. }
928 | ASTNodeType::Literal(_)
929 | ASTNodeType::Omitted => Err(ExcelError::new(ExcelErrorKind::Ref)
930 .with_message("Expression cannot be used as a reference")),
931 }
932 }
933
934 pub(crate) fn try_evaluate_ast_as_reference(
935 &self,
936 node: &ASTNode,
937 ) -> Option<Result<ReferenceType, ExcelError>> {
938 let ASTNodeType::Function { name, args } = &node.node_type else {
939 return Some(self.evaluate_ast_as_reference(node));
940 };
941 let fun = match self.ast_function(name) {
942 Some(fun) => fun,
943 None => {
944 self.unknown_function(name);
945 return Some(Err(ExcelError::new(ExcelErrorKind::Name)
946 .with_message(format!("Unknown function: {name}"))));
947 }
948 };
949 let handles: smallvec::SmallVec<[ArgumentHandle; 4]> = args
950 .iter()
951 .map(|arg| ArgumentHandle::new(arg, self))
952 .collect();
953 let fctx = DefaultFunctionContext::new_with_sheet(self.context, None, self.current_sheet);
954 let _origin = self.reference_diagnostic_scope();
955 self.diagnostic_dispatch(
956 name,
957 &*fun,
958 fun.reports_known_limitations(),
959 || fun.eval_reference(&handles, &fctx),
960 crate::operation_diagnostics::is_unimplemented_reference,
961 )
962 }
963
964 pub(crate) fn evaluate_arena_ast_as_reference(
965 &self,
966 node_id: AstNodeId,
967 data_store: &DataStore,
968 sheet_registry: &SheetRegistry,
969 ) -> Result<ReferenceType, ExcelError> {
970 let _root = crate::external_callbacks::root(self.current_cell);
971 if let Some(error) = crate::external_callbacks::pending() {
972 return Err(error);
973 }
974
975 let node = data_store.get_node(node_id).ok_or_else(|| {
976 ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
977 })?;
978
979 match node {
980 AstNodeData::Reference { ref_type, .. } => {
981 let ref_type = data_store.compact_ref(*ref_type);
982 let reference =
983 data_store.reconstruct_reference_type_for_eval(ref_type, sheet_registry);
984 self.reference_for_current_offset(&reference)
985 }
986 AstNodeData::Function { name_id, .. } => {
987 let fun = self.arena_function(*name_id, data_store).ok_or_else(|| {
988 let name = data_store.resolve_ast_string(*name_id);
989 if name == CALL_NODE_NAME {
990 self.unsupported_call()
991 } else {
992 self.unknown_function(name);
993 ExcelError::new(ExcelErrorKind::Name)
994 .with_message(format!("Unknown function: {name}"))
995 }
996 })?;
997
998 let args = data_store.get_args(node_id).ok_or_else(|| {
999 ExcelError::new(ExcelErrorKind::Value).with_message("Missing function args")
1000 })?;
1001
1002 let handles: smallvec::SmallVec<[ArgumentHandle; 4]> = args
1003 .iter()
1004 .copied()
1005 .map(|arg_id| {
1006 ArgumentHandle::new_arena(arg_id, self, data_store, sheet_registry)
1007 })
1008 .collect();
1009
1010 let fctx =
1011 DefaultFunctionContext::new_with_sheet(self.context, None, self.current_sheet);
1012
1013 let _origin = self.reference_diagnostic_scope();
1014 self.diagnostic_dispatch(
1015 data_store.resolve_ast_string(*name_id),
1016 &*fun,
1017 fun.reports_known_limitations(),
1018 || fun.eval_reference(&handles, &fctx),
1019 crate::operation_diagnostics::is_unimplemented_reference,
1020 )
1021 .ok_or_else(|| {
1022 ExcelError::new(ExcelErrorKind::Ref)
1023 .with_message("Function does not return a reference")
1024 })?
1025 }
1026 AstNodeData::BinaryOp {
1027 operator,
1028 left_id,
1029 right_id,
1030 } => {
1031 let op = *operator;
1032 if op != BinaryOperator::Range {
1033 return Err(ExcelError::new(ExcelErrorKind::Ref)
1034 .with_message("Expression cannot be used as a reference"));
1035 }
1036 let lref =
1037 self.evaluate_arena_ast_as_reference(*left_id, data_store, sheet_registry)?;
1038 let rref =
1039 self.evaluate_arena_ast_as_reference(*right_id, data_store, sheet_registry)?;
1040 crate::reference::combine_references(&lref, &rref)
1041 }
1042 AstNodeData::UnaryOp { operator, expr_id } if *operator == UnaryOperator::Spill => {
1043 self.arena_spill_reference(*expr_id, data_store, sheet_registry)
1044 }
1045 _ => Err(ExcelError::new(ExcelErrorKind::Ref)
1046 .with_message("Expression cannot be used as a reference")),
1047 }
1048 }
1049
1050 fn spill_anchor_operand(&self, reference: &ReferenceType) -> Result<ReferenceType, ExcelError> {
1053 let anchor = match reference {
1054 ReferenceType::NamedRange(name) if self.resolve_local_name(name).is_some() => self
1055 .resolve_local_bound_reference(name)
1056 .ok_or_else(spill_operand_error)?,
1057 _ => self
1058 .reference_for_current_offset(reference)
1059 .map_err(|_| spill_operand_error())?,
1060 };
1061 match anchor {
1062 ReferenceType::Cell { .. } | ReferenceType::NamedRange(_) => Ok(anchor),
1063 _ => Err(spill_operand_error()),
1064 }
1065 }
1066
1067 pub(crate) fn resolve_spill_reference(
1070 &self,
1071 reference: &ReferenceType,
1072 ) -> Result<ReferenceType, ExcelError> {
1073 let anchor = self.spill_anchor_operand(reference)?;
1074 self.context
1075 .resolve_spill_reference(&anchor, self.current_sheet)
1076 }
1077
1078 pub(crate) fn ast_spill_reference(
1080 &self,
1081 operand: &ASTNode,
1082 ) -> Result<ReferenceType, ExcelError> {
1083 match &operand.node_type {
1084 ASTNodeType::Reference { reference, .. } => self.resolve_spill_reference(reference),
1085 _ => Err(spill_operand_error()),
1086 }
1087 }
1088
1089 pub(crate) fn arena_spill_reference(
1091 &self,
1092 operand: AstNodeId,
1093 data_store: &DataStore,
1094 sheet_registry: &SheetRegistry,
1095 ) -> Result<ReferenceType, ExcelError> {
1096 match data_store.get_node(operand) {
1097 Some(AstNodeData::Reference { ref_type, .. }) => {
1098 let ref_type = data_store.compact_ref(*ref_type);
1099 let reference =
1100 data_store.reconstruct_reference_type_for_eval(ref_type, sheet_registry);
1101 self.resolve_spill_reference(&reference)
1102 }
1103 _ => Err(spill_operand_error()),
1104 }
1105 }
1106
1107 pub(crate) fn try_evaluate_arena_ast_as_reference(
1108 &self,
1109 node_id: AstNodeId,
1110 data_store: &DataStore,
1111 sheet_registry: &SheetRegistry,
1112 ) -> Option<Result<ReferenceType, ExcelError>> {
1113 let node = match data_store.get_node(node_id) {
1114 Some(node) => node,
1115 None => {
1116 return Some(Err(
1117 ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
1118 ));
1119 }
1120 };
1121 let AstNodeData::Function { name_id, .. } = node else {
1122 return Some(self.evaluate_arena_ast_as_reference(node_id, data_store, sheet_registry));
1123 };
1124 let fun = match self.arena_function(*name_id, data_store) {
1125 Some(fun) => fun,
1126 None => {
1127 let name = data_store.resolve_ast_string(*name_id);
1128 if name == CALL_NODE_NAME {
1129 return Some(Err(self.unsupported_call()));
1130 }
1131 self.unknown_function(name);
1132 return Some(Err(ExcelError::new(ExcelErrorKind::Name)
1133 .with_message(format!("Unknown function: {name}"))));
1134 }
1135 };
1136 let args = match data_store.get_args(node_id) {
1137 Some(args) => args,
1138 None => {
1139 return Some(Err(
1140 ExcelError::new(ExcelErrorKind::Value).with_message("Missing function args")
1141 ));
1142 }
1143 };
1144 let handles: smallvec::SmallVec<[ArgumentHandle; 4]> = args
1145 .iter()
1146 .copied()
1147 .map(|arg_id| ArgumentHandle::new_arena(arg_id, self, data_store, sheet_registry))
1148 .collect();
1149 let fctx = DefaultFunctionContext::new_with_sheet(self.context, None, self.current_sheet);
1150 let _origin = self.reference_diagnostic_scope();
1151 self.diagnostic_dispatch(
1152 data_store.resolve_ast_string(*name_id),
1153 &*fun,
1154 fun.reports_known_limitations(),
1155 || fun.eval_reference(&handles, &fctx),
1156 crate::operation_diagnostics::is_unimplemented_reference,
1157 )
1158 }
1159
1160 pub fn evaluate_ast(&self, node: &ASTNode) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1162 let _root = crate::external_callbacks::root(self.current_cell);
1163 if let Some(error) = crate::external_callbacks::pending() {
1164 return Err(error);
1165 }
1166
1167 let result = self.evaluate_ast_uncached(node);
1168 crate::external_callbacks::pending().map_or(result, Err)
1169 }
1170
1171 pub(crate) fn evaluate_ast_with_offset(
1172 &self,
1173 node: &ASTNode,
1174 row_delta: i64,
1175 col_delta: i64,
1176 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1177 let offset = Self {
1178 context: self.context,
1179 diagnostics: self.diagnostics,
1180 resolved_functions: self.resolved_functions,
1181 arena_cells: self.arena_cells,
1182 current_sheet: self.current_sheet,
1183 current_cell: self.current_cell,
1184 local_env: self.local_env.clone(),
1185 reference_row_delta: row_delta,
1186 reference_col_delta: col_delta,
1187 disable_ast_planner: true,
1188 parameter_bindings: self.parameter_bindings,
1189 };
1190 offset.evaluate_ast(node)
1191 }
1192
1193 pub(crate) fn reference_for_current_offset(
1194 &self,
1195 reference: &ReferenceType,
1196 ) -> Result<ReferenceType, ExcelError> {
1197 self.effective_reference(reference)
1198 .map(|reference| reference.into_owned())
1199 }
1200
1201 pub(crate) fn evaluate_formula_view(
1204 &self,
1205 view: crate::engine::graph::FormulaView,
1206 data_store: &DataStore,
1207 sheet_registry: &SheetRegistry,
1208 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1209 if view.row_delta == 0 && view.col_delta == 0 {
1210 self.evaluate_arena_ast(view.template, data_store, sheet_registry)
1211 } else {
1212 self.evaluate_arena_ast_with_offset(
1213 view.template,
1214 view.row_delta,
1215 view.col_delta,
1216 data_store,
1217 sheet_registry,
1218 )
1219 }
1220 }
1221
1222 pub(crate) fn evaluate_arena_ast_with_offset(
1223 &self,
1224 node_id: AstNodeId,
1225 row_delta: i64,
1226 col_delta: i64,
1227 data_store: &DataStore,
1228 sheet_registry: &SheetRegistry,
1229 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1230 let offset = Self {
1231 context: self.context,
1232 diagnostics: self.diagnostics,
1233 resolved_functions: self.resolved_functions,
1234 arena_cells: self.arena_cells,
1235 current_sheet: self.current_sheet,
1236 current_cell: self.current_cell,
1237 local_env: self.local_env.clone(),
1238 reference_row_delta: row_delta,
1239 reference_col_delta: col_delta,
1240 disable_ast_planner: true,
1241 parameter_bindings: self.parameter_bindings,
1242 };
1243 offset.evaluate_arena_ast(node_id, data_store, sheet_registry)
1244 }
1245
1246 #[inline]
1247 fn annotated(
1248 value: LiteralValue,
1249 format: Option<crate::format::FormatId>,
1250 ) -> crate::traits::CalcValue<'a> {
1251 match format {
1252 Some(format) => crate::traits::CalcValue::AnnotatedScalar(value, format),
1253 None => crate::traits::CalcValue::Scalar(value),
1254 }
1255 }
1256
1257 fn annotate_cell_value(
1258 &self,
1259 sheet: Option<&str>,
1260 row: u32,
1261 col: u32,
1262 value: LiteralValue,
1263 ) -> crate::traits::CalcValue<'a> {
1264 match self
1265 .context
1266 .resolve_cell_format(sheet, row, col, self.current_sheet)
1267 {
1268 Some(format) => crate::traits::CalcValue::AnnotatedScalar(value, format),
1269 None => crate::traits::CalcValue::Scalar(value),
1270 }
1271 }
1272
1273 fn binary_format(
1274 &self,
1275 op: char,
1276 left: Option<crate::format::FormatId>,
1277 right: Option<crate::format::FormatId>,
1278 ) -> Option<crate::format::FormatId> {
1279 use formualizer_common::numfmt::FormatClass;
1280 let class =
1281 |id: Option<crate::format::FormatId>| id.and_then(|id| self.context.format_class(id));
1282 let left = class(left);
1283 let right = class(right);
1284 let is_plain = |class: &Option<FormatClass>| {
1285 matches!(
1286 class,
1287 None | Some(FormatClass::General | FormatClass::Number { .. })
1288 )
1289 };
1290 match (op, left.as_ref(), right.as_ref()) {
1295 ('+', Some(FormatClass::Date), Some(FormatClass::Time))
1296 | ('+', Some(FormatClass::Time), Some(FormatClass::Date)) => {
1297 Some(crate::format::FormatId::DATETIME)
1298 }
1299 ('+', Some(FormatClass::Date), Some(FormatClass::Percent { .. }))
1300 | ('+', Some(FormatClass::Percent { .. }), Some(FormatClass::Date)) => {
1301 Some(crate::format::FormatId::DATE)
1302 }
1303 ('+' | '-', Some(FormatClass::Date), r) if is_plain(&r.cloned()) => {
1304 Some(crate::format::FormatId::DATE)
1305 }
1306 ('+', l, Some(FormatClass::Date)) if is_plain(&l.cloned()) => {
1307 Some(crate::format::FormatId::DATE)
1308 }
1309 ('+' | '-', Some(FormatClass::Time), r) if is_plain(&r.cloned()) => {
1310 Some(crate::format::FormatId::TIME)
1311 }
1312 ('+', l, Some(FormatClass::Time)) if is_plain(&l.cloned()) => {
1313 Some(crate::format::FormatId::TIME)
1314 }
1315 ('+' | '-', Some(FormatClass::DateTime), r) if is_plain(&r.cloned()) => {
1316 Some(crate::format::FormatId::DATETIME)
1317 }
1318 ('+', l, Some(FormatClass::DateTime)) if is_plain(&l.cloned()) => {
1319 Some(crate::format::FormatId::DATETIME)
1320 }
1321 ('+' | '-', Some(FormatClass::Duration), r) if is_plain(&r.cloned()) => {
1322 Some(crate::format::FormatId::DURATION)
1323 }
1324 ('+', l, Some(FormatClass::Duration)) if is_plain(&l.cloned()) => {
1325 Some(crate::format::FormatId::DURATION)
1326 }
1327 _ => None,
1328 }
1329 }
1330
1331 fn annotate_numeric_result(
1332 &self,
1333 value: LiteralValue,
1334 format: Option<crate::format::FormatId>,
1335 ) -> crate::traits::CalcValue<'a> {
1336 match (value, format) {
1337 (value @ LiteralValue::Number(_), Some(format)) => {
1338 crate::traits::CalcValue::AnnotatedScalar(value, format)
1339 }
1340 (value, _) => crate::traits::CalcValue::Scalar(value),
1341 }
1342 }
1343
1344 pub(crate) fn apply_unary_op(
1347 &self,
1348 op: UnaryOperator,
1349 expr: crate::traits::CalcValue<'a>,
1350 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1351 let v = expr.into_literal();
1353 match v {
1354 LiteralValue::Array(arr) => self
1355 .map_array(arr, |cell| self.eval_unary_scalar(op, cell))
1356 .map(crate::traits::CalcValue::Scalar),
1357 other => self
1358 .eval_unary_scalar(op, other)
1359 .map(crate::traits::CalcValue::Scalar),
1360 }
1361 }
1362
1363 pub(crate) fn apply_binary_op(
1366 &self,
1367 op: BinaryOperator,
1368 left: LiteralValue,
1369 left_format: Option<crate::format::FormatId>,
1370 right: LiteralValue,
1371 right_format: Option<crate::format::FormatId>,
1372 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1373 if matches!(
1374 op,
1375 BinaryOperator::Equal
1376 | BinaryOperator::NotEqual
1377 | BinaryOperator::Greater
1378 | BinaryOperator::Less
1379 | BinaryOperator::GreaterEqual
1380 | BinaryOperator::LessEqual
1381 ) {
1382 return self
1383 .compare(op, left, right)
1384 .map(crate::traits::CalcValue::Scalar);
1385 }
1386
1387 match op {
1388 BinaryOperator::Add | BinaryOperator::Subtract => {
1389 let byte = if op == BinaryOperator::Add {
1390 b'+'
1391 } else {
1392 b'-'
1393 };
1394 let value = self.numeric_binary(left, right, byte)?;
1395 if left_format.is_none() && right_format.is_none() {
1396 Ok(crate::traits::CalcValue::Scalar(value))
1400 } else {
1401 Ok(self.annotate_numeric_result(
1402 value,
1403 self.binary_format(char::from(byte), left_format, right_format),
1404 ))
1405 }
1406 }
1407 BinaryOperator::Multiply => self
1408 .numeric_binary(left, right, b'*')
1409 .map(crate::traits::CalcValue::Scalar),
1410 BinaryOperator::Divide => self
1411 .numeric_binary(left, right, b'/')
1412 .map(crate::traits::CalcValue::Scalar),
1413 BinaryOperator::Power => self
1414 .numeric_binary(left, right, b'^')
1415 .map(crate::traits::CalcValue::Scalar),
1416 BinaryOperator::Concat => self
1417 .concat_values(left, right)
1418 .map(crate::traits::CalcValue::Scalar),
1419 _ => {
1420 Err(ExcelError::new(ExcelErrorKind::NImpl)
1421 .with_message(format!("Binary op '{op}'")))
1422 }
1423 }
1424 }
1425
1426 pub(crate) fn resolve_arena_argument_range(
1429 &self,
1430 node_id: AstNodeId,
1431 data_store: &DataStore,
1432 ) -> Option<Result<crate::engine::range_view::RangeView<'a>, ExcelError>> {
1433 let resolver = self.arena_cells?;
1434 let cell = self.current_cell?;
1435 if !self.local_env.is_empty() {
1436 return None;
1437 }
1438 let AstNodeData::Reference { ref_type, .. } = data_store.get_node(node_id)? else {
1439 return None;
1440 };
1441 let reference = data_store.compact_ref(*ref_type);
1442 if !matches!(reference, CompactRefType::Range { .. }) {
1443 return None;
1444 }
1445 Some(resolver.resolve_arena_range(
1446 reference,
1447 cell.sheet_id,
1448 self.reference_row_delta,
1449 self.reference_col_delta,
1450 ))
1451 }
1452
1453 pub(crate) fn evaluate_arena_ast(
1454 &self,
1455 node_id: AstNodeId,
1456 data_store: &DataStore,
1457 sheet_registry: &SheetRegistry,
1458 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1459 let _root = crate::external_callbacks::root(self.current_cell);
1460 if let Some(error) = crate::external_callbacks::pending() {
1461 return Err(error);
1462 }
1463
1464 let result = self.evaluate_arena_ast_inner(node_id, data_store, sheet_registry);
1465 crate::external_callbacks::pending().map_or(result, Err)
1466 }
1467
1468 pub(crate) fn evaluate_arena_ast_inner(
1469 &self,
1470 node_id: AstNodeId,
1471 data_store: &DataStore,
1472 sheet_registry: &SheetRegistry,
1473 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1474 let node = data_store.get_node(node_id).ok_or_else(|| {
1475 ExcelError::new(ExcelErrorKind::Value).with_message("Missing AST node")
1476 })?;
1477
1478 match node {
1479 AstNodeData::Literal(vref) => {
1480 if let Some(bindings) = self.parameter_bindings
1481 && let Some(slot_id) = bindings.literal_slots_by_node.get(&node_id)
1482 && let Some(value) = bindings.literal_values.get(slot_id.0 as usize)
1483 {
1484 return Ok(crate::traits::CalcValue::Scalar(value.clone()));
1485 }
1486 Ok(crate::traits::CalcValue::Scalar(
1487 data_store.retrieve_value(*vref),
1488 ))
1489 }
1490 AstNodeData::Omitted => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0))),
1491 AstNodeData::Reference { ref_type, .. } => {
1492 let ref_type = data_store.compact_ref(*ref_type);
1493 if self.local_env.is_empty()
1494 && matches!(ref_type, CompactRefType::Range { .. })
1495 && let (Some(resolver), Some(cell)) = (self.arena_cells, self.current_cell)
1496 {
1497 let view = resolver
1498 .resolve_arena_range(
1499 ref_type,
1500 cell.sheet_id,
1501 self.reference_row_delta,
1502 self.reference_col_delta,
1503 )?
1504 .with_cancel_token(self.context.cancellation_token())
1505 .with_cell_budget(self.context.materialized_array_cells());
1506 return Ok(crate::traits::CalcValue::Range(view));
1507 }
1508 if self.local_env.is_empty()
1509 && let CompactRefType::Cell {
1510 sheet,
1511 row,
1512 col,
1513 row_abs,
1514 col_abs,
1515 } = ref_type
1516 && *row > 0
1517 && *col > 0
1518 {
1519 let row = shift_axis_for_offset(*row, self.reference_row_delta, *row_abs)?;
1520 let col = shift_axis_for_offset(*col, self.reference_col_delta, *col_abs)?;
1521 if let (Some(resolver), Some(cell)) = (self.arena_cells, self.current_cell) {
1522 let (value, format) =
1523 resolver.resolve_arena_cell(*sheet, cell.sheet_id, row, col)?;
1524 return Ok(Self::annotated(value, format));
1525 }
1526 let sheet_name = match sheet {
1527 Some(SheetKey::Id(id)) => Some(sheet_registry.name(*id)),
1528 Some(SheetKey::Name(name_id)) => {
1529 Some(data_store.resolve_ast_string(*name_id))
1530 }
1531 None => None,
1532 };
1533 let (value, format) = self.context.resolve_cell_reference_value_formatted(
1534 sheet_name,
1535 row,
1536 col,
1537 self.current_sheet,
1538 )?;
1539 Ok(Self::annotated(value, format))
1540 } else {
1541 let reference =
1542 data_store.reconstruct_reference_type_for_eval(ref_type, sheet_registry);
1543 let reference = self.effective_reference(&reference)?;
1544 if let Some(local) = self.resolve_local_reference(&reference) {
1545 return Ok(local);
1546 }
1547 self.eval_reference_to_calc(&reference)
1548 }
1549 }
1550 AstNodeData::UnaryOp { operator, expr_id } => {
1551 let op = *operator;
1552 if op == UnaryOperator::Spill {
1553 let reference =
1554 self.arena_spill_reference(*expr_id, data_store, sheet_registry)?;
1555 return self.eval_reference_to_calc(&reference);
1556 }
1557 if op == UnaryOperator::ImplicitIntersection {
1558 if let Some(AstNodeData::Reference { ref_type, .. }) =
1564 data_store.get_node(*expr_id)
1565 {
1566 let reference = data_store.reconstruct_reference_type_for_eval(
1567 data_store.compact_ref(*ref_type),
1568 sheet_registry,
1569 );
1570 let v = match self.effective_reference(&reference) {
1571 Ok(reference) => self.implicit_intersection_from_reference(&reference),
1572 Err(e) => LiteralValue::Error(e),
1573 };
1574 return Ok(crate::traits::CalcValue::Scalar(v));
1575 }
1576
1577 let expr =
1578 self.evaluate_arena_ast_inner(*expr_id, data_store, sheet_registry)?;
1579 let v = self.eval_implicit_intersection_calc(expr);
1580 return Ok(crate::traits::CalcValue::Scalar(v));
1581 }
1582 let expr = self.evaluate_arena_ast_inner(*expr_id, data_store, sheet_registry)?;
1583 self.apply_unary_op(op, expr)
1584 }
1585 AstNodeData::BinaryOp {
1586 operator,
1587 left_id,
1588 right_id,
1589 } => {
1590 let op = *operator;
1591 if op == BinaryOperator::Range {
1592 let lref =
1593 self.evaluate_arena_ast_as_reference(*left_id, data_store, sheet_registry)?;
1594 let rref = self.evaluate_arena_ast_as_reference(
1595 *right_id,
1596 data_store,
1597 sheet_registry,
1598 )?;
1599 return match crate::reference::combine_references(&lref, &rref) {
1600 Ok(_r) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
1601 ExcelError::new(ExcelErrorKind::Ref).with_message(
1602 "Reference produced by ':' cannot be used directly as a value",
1603 ),
1604 ))),
1605 Err(e) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(e))),
1606 };
1607 }
1608
1609 let left_calc =
1610 self.evaluate_arena_ast_inner(*left_id, data_store, sheet_registry)?;
1611 let left_format = left_calc.format_id();
1612 let left = left_calc.into_literal();
1613 let right_calc =
1614 self.evaluate_arena_ast_inner(*right_id, data_store, sheet_registry)?;
1615 let right_format = right_calc.format_id();
1616 let right = right_calc.into_literal();
1617 self.apply_binary_op(op, left, left_format, right, right_format)
1618 }
1619 AstNodeData::Array { .. } => {
1620 let (rows, cols, elements) =
1621 data_store.get_array_elems(node_id).ok_or_else(|| {
1622 ExcelError::new(ExcelErrorKind::Value).with_message("Invalid array")
1623 })?;
1624
1625 let rows_usize = rows as usize;
1626 let cols_usize = cols as usize;
1627 let mut out: Vec<Vec<LiteralValue>> = Vec::with_capacity(rows_usize);
1628 for r in 0..rows_usize {
1629 let mut row = Vec::with_capacity(cols_usize);
1630 for c in 0..cols_usize {
1631 let idx = r * cols_usize + c;
1632 if let Some(&elem_id) = elements.get(idx) {
1633 row.push(
1634 self.evaluate_arena_ast_inner(elem_id, data_store, sheet_registry)?
1635 .into_literal(),
1636 );
1637 }
1638 }
1639 out.push(row);
1640 }
1641
1642 Ok(crate::traits::CalcValue::Range(
1643 crate::engine::range_view::RangeView::from_owned_rows(
1644 out,
1645 self.context.date_system(),
1646 ),
1647 ))
1648 }
1649 AstNodeData::Function { name_id, .. } => {
1650 if let Some(bindings) = self.parameter_bindings
1651 && let Some(values) = bindings.invariant_values
1652 && let Some((value, format)) = values.get(&node_id)
1653 {
1654 return Ok(Self::annotated(value.clone(), *format));
1655 }
1656 let function = self.arena_function(*name_id, data_store);
1657 if function.is_none() && data_store.resolve_ast_string(*name_id) == CALL_NODE_NAME {
1658 return Err(self.unsupported_call());
1659 }
1660 let args = data_store.get_args(node_id).ok_or_else(|| {
1661 ExcelError::new(ExcelErrorKind::Value).with_message("Missing function args")
1662 })?;
1663
1664 if let Some(fun) = function {
1665 let handles: smallvec::SmallVec<[ArgumentHandle; 4]> = args
1666 .iter()
1667 .copied()
1668 .map(|arg_id| {
1669 ArgumentHandle::new_arena(arg_id, self, data_store, sheet_registry)
1670 })
1671 .collect();
1672
1673 let fctx = DefaultFunctionContext::new_with_sheet(
1674 self.context,
1675 self.current_cell,
1676 self.current_sheet,
1677 );
1678
1679 return self.diagnostic_dispatch(
1680 data_store.resolve_ast_string(*name_id),
1681 &*fun,
1682 fun.reports_known_limitations(),
1683 || fun.dispatch(&handles, &fctx),
1684 crate::operation_diagnostics::is_unimplemented,
1685 );
1686 }
1687
1688 let name = data_store.resolve_ast_string(*name_id);
1689 if let Some(callable) = self.resolve_local_callable(name) {
1690 let mut eval_args = Vec::with_capacity(args.len());
1691 let mut arg_references = Vec::with_capacity(args.len());
1692 for arg_id in args {
1693 arg_references.push(
1695 ArgumentHandle::new_arena(*arg_id, self, data_store, sheet_registry)
1696 .bound_reference_in_env(&self.local_env),
1697 );
1698 eval_args.push(
1699 self.evaluate_arena_ast_inner(*arg_id, data_store, sheet_registry)?
1700 .into_literal(),
1701 );
1702 }
1703 return self.function_call(name, || {
1704 callable.invoke_with_references(self, &eval_args, &arg_references)
1705 });
1706 }
1707
1708 self.unknown_function(name);
1709 Err(ExcelError::new(ExcelErrorKind::Name)
1710 .with_message(format!("Unknown function: {name}")))
1711 }
1712 }
1713 }
1714
1715 pub(crate) fn evaluate_ast_uncached(
1716 &self,
1717 node: &ASTNode,
1718 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1719 if self.disable_ast_planner {
1720 return self.eval_tree_uncached(node);
1721 }
1722
1723 let current_sheet = self.current_sheet.to_string();
1727 let range_probe = |reference: &ReferenceType| {
1728 probe_range_dimensions(self.context, ¤t_sheet, reference)
1729 };
1730 let fn_lookup = |ns: &str, name: &str| self.ast_function_arc(ns, name);
1731
1732 let mut planner = crate::planner::Planner::new(crate::planner::PlanConfig::default())
1733 .with_range_probe(&range_probe)
1734 .with_function_lookup(&fn_lookup);
1735 let plan = planner.plan(node);
1736 self.eval_with_plan(node, &plan.root)
1737 }
1738
1739 fn eval_tree_uncached(
1740 &self,
1741 node: &ASTNode,
1742 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1743 match &node.node_type {
1744 ASTNodeType::Literal(v) => Ok(crate::traits::CalcValue::Scalar(v.clone())),
1745 ASTNodeType::Omitted => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0))),
1746 ASTNodeType::Reference { reference, .. } => self.eval_ast_reference_to_calc(reference),
1747 ASTNodeType::UnaryOp { op, expr } if *op == UnaryOperator::Spill => {
1748 self.eval_reference_to_calc(&self.ast_spill_reference(expr)?)
1749 }
1750 ASTNodeType::UnaryOp { op, expr } => self
1751 .eval_unary(*op, expr)
1752 .map(crate::traits::CalcValue::Scalar),
1753 ASTNodeType::BinaryOp { op, left, right } => self.eval_binary(*op, left, right),
1754 ASTNodeType::Function { name, args } => self.eval_function_to_calc(name, args),
1755 ASTNodeType::Call { .. } => Err(self.unsupported_call()),
1756 ASTNodeType::Array(rows) => self.eval_array_literal_to_calc(rows),
1757 }
1758 }
1759
1760 fn eval_with_plan(
1761 &self,
1762 node: &ASTNode,
1763 plan_node: &crate::planner::PlanNode,
1764 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1765 match &node.node_type {
1766 ASTNodeType::Literal(v) => Ok(crate::traits::CalcValue::Scalar(v.clone())),
1767 ASTNodeType::Omitted => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(0.0))),
1768 ASTNodeType::Reference { reference, .. } => self.eval_ast_reference_to_calc(reference),
1769 ASTNodeType::UnaryOp { op, expr } if *op == UnaryOperator::Spill => {
1770 self.eval_reference_to_calc(&self.ast_spill_reference(expr)?)
1771 }
1772 ASTNodeType::UnaryOp { op, expr } => {
1773 self.eval_unary(*op, expr)
1776 .map(crate::traits::CalcValue::Scalar)
1777 }
1778 ASTNodeType::BinaryOp { op, left, right } => self.eval_binary(*op, left, right),
1779 ASTNodeType::Function { name, args } => {
1780 let strategy = plan_node.strategy;
1781 if let Some(fun) = self.ast_function(name) {
1782 use crate::function::FnCaps;
1783 use crate::planner::ExecStrategy;
1784 let caps = crate::external_callbacks::metadata(
1785 &*fun,
1786 crate::function::FnCaps::empty,
1787 || fun.caps(),
1788 );
1789
1790 if caps.contains(FnCaps::SHORT_CIRCUIT) || caps.contains(FnCaps::VOLATILE) {
1792 return self.eval_function_to_calc(name, args);
1793 }
1794
1795 if matches!(strategy, ExecStrategy::ArgParallel)
1799 && caps.contains(FnCaps::PARALLEL_ARGS)
1800 {
1801 for arg in args {
1803 match &arg.node_type {
1804 ASTNodeType::Reference { reference, .. } => {
1805 if let Ok(reference) = self.effective_reference(reference) {
1806 let _ = self
1807 .context
1808 .resolve_range_view(&reference, self.current_sheet);
1809 }
1810 }
1811 _ => {
1812 let _ = self.evaluate_ast_uncached(arg);
1813 }
1814 }
1815 }
1816 return self.eval_function_to_calc(name, args);
1817 }
1818
1819 return self.eval_function_to_calc(name, args);
1821 }
1822 self.eval_function_to_calc(name, args)
1823 }
1824 ASTNodeType::Call { .. } => Err(self.unsupported_call()),
1825 ASTNodeType::Array(rows) => self.eval_array_literal_to_calc(rows),
1826 }
1827 }
1828
1829 fn eval_ast_reference_to_calc(
1831 &self,
1832 reference: &ReferenceType,
1833 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1834 if !self.local_env.is_empty() {
1835 let reference = self.effective_reference(reference)?;
1836 if let Some(local) = self.resolve_local_reference(&reference) {
1837 return Ok(local);
1838 }
1839 return self.eval_reference_to_calc(&reference);
1840 }
1841
1842 if let ReferenceType::Cell {
1843 sheet,
1844 row,
1845 col,
1846 row_abs,
1847 col_abs,
1848 } = reference
1849 {
1850 let row = shift_axis_for_offset(*row, self.reference_row_delta, *row_abs)?;
1851 let col = shift_axis_for_offset(*col, self.reference_col_delta, *col_abs)?;
1852 let (value, format) = self.context.resolve_cell_reference_value_formatted(
1853 sheet.as_deref(),
1854 row,
1855 col,
1856 self.current_sheet,
1857 )?;
1858 return Ok(Self::annotated(value, format));
1859 }
1860
1861 let reference = self.effective_reference(reference)?;
1862 self.eval_reference_to_calc(&reference)
1863 }
1864
1865 fn eval_reference_to_calc(
1866 &self,
1867 reference: &ReferenceType,
1868 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
1869 if let ReferenceType::Cell {
1870 sheet, row, col, ..
1871 } = reference
1872 {
1873 let value = self.context.resolve_cell_reference_value(
1874 sheet.as_deref(),
1875 *row,
1876 *col,
1877 self.current_sheet,
1878 )?;
1879 return Ok(self.annotate_cell_value(sheet.as_deref(), *row, *col, value));
1880 }
1881
1882 let view = self
1883 .context
1884 .resolve_range_view(reference, self.current_sheet)?
1885 .with_cancel_token(self.context.cancellation_token())
1886 .with_cell_budget(self.context.materialized_array_cells());
1887 Ok(crate::traits::CalcValue::Range(view))
1888 }
1889
1890 fn eval_reference(&self, reference: &ReferenceType) -> Result<LiteralValue, ExcelError> {
1891 self.eval_reference_to_calc(reference)
1892 .map(|cv| cv.into_literal())
1893 }
1894
1895 fn eval_unary(&self, op: UnaryOperator, expr: &ASTNode) -> Result<LiteralValue, ExcelError> {
1897 if op == UnaryOperator::ImplicitIntersection {
1898 if let ASTNodeType::Reference { reference, .. } = &expr.node_type {
1899 let reference = self.effective_reference(reference)?;
1900 return Ok(self.implicit_intersection_from_reference(&reference));
1901 }
1902
1903 let cv = self.evaluate_ast_uncached(expr)?;
1904 return Ok(self.eval_implicit_intersection_calc(cv));
1905 }
1906
1907 let v = self.evaluate_ast_uncached(expr)?.into_literal();
1908 match v {
1909 LiteralValue::Array(arr) => {
1910 self.map_array(arr, |cell| self.eval_unary_scalar(op, cell))
1911 }
1912 other => self.eval_unary_scalar(op, other),
1913 }
1914 }
1915
1916 fn eval_unary_scalar(
1917 &self,
1918 op: UnaryOperator,
1919 v: LiteralValue,
1920 ) -> Result<LiteralValue, ExcelError> {
1921 match op {
1922 UnaryOperator::Plus => Ok(v),
1927 UnaryOperator::Minus => self.apply_number_unary(v, b'-'),
1928 UnaryOperator::Percent => self.apply_number_unary(v, b'%'),
1929 _ => {
1930 Err(ExcelError::new(ExcelErrorKind::NImpl).with_message(format!("Unary op '{op}'")))
1931 }
1932 }
1933 }
1934
1935 fn eval_implicit_intersection_calc(&self, cv: crate::traits::CalcValue<'a>) -> LiteralValue {
1936 let (cur_r0, cur_c0) = match self.current_cell {
1937 Some(cell) => (cell.coord.row() as usize, cell.coord.col() as usize),
1938 None => (0usize, 0usize),
1939 };
1940
1941 match cv {
1942 crate::traits::CalcValue::Scalar(v)
1943 | crate::traits::CalcValue::AnnotatedScalar(v, _) => match v {
1944 LiteralValue::Array(arr) => {
1945 if arr.is_empty() || arr.first().map(|r| r.is_empty()).unwrap_or(true) {
1946 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1947 }
1948 arr[0][0].clone()
1949 }
1950 other => other,
1951 },
1952 crate::traits::CalcValue::Range(rv) => {
1953 if rv.is_empty() {
1954 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1955 }
1956
1957 if rv.sheet_name() == "__tmp" {
1962 return rv.get_cell(0, 0);
1963 }
1964
1965 if let Some(v) = rv.as_1x1() {
1966 return v;
1967 }
1968
1969 let (rows, cols) = rv.dims();
1970 let sr = rv.start_row();
1971 let sc = rv.start_col();
1972 let er = rv.end_row();
1973 let ec = rv.end_col();
1974
1975 if cols == 1 {
1980 if cur_r0 < sr || cur_r0 > er {
1981 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1982 }
1983 let rel_r = cur_r0 - sr;
1984 return rv.get_cell(rel_r, 0);
1985 }
1986
1987 if rows == 1 {
1988 if cur_c0 < sc || cur_c0 > ec {
1989 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1990 }
1991 let rel_c = cur_c0 - sc;
1992 return rv.get_cell(0, rel_c);
1993 }
1994
1995 if cur_r0 < sr || cur_r0 > er || cur_c0 < sc || cur_c0 > ec {
1996 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
1997 }
1998 let rel_r = cur_r0 - sr;
1999 let rel_c = cur_c0 - sc;
2000 rv.get_cell(rel_r, rel_c)
2001 }
2002 crate::traits::CalcValue::Callable(_) => LiteralValue::Error(
2003 ExcelError::new(ExcelErrorKind::Calc).with_message("LAMBDA value must be invoked"),
2004 ),
2005 }
2006 }
2007
2008 fn implicit_intersection_from_reference(&self, reference: &ReferenceType) -> LiteralValue {
2009 let (cur_r1, cur_c1) = match self.current_cell {
2010 Some(cell) => (
2011 cell.coord.row().saturating_add(1),
2012 cell.coord.col().saturating_add(1),
2013 ),
2014 None => (1u32, 1u32),
2015 };
2016
2017 match reference {
2018 ReferenceType::Cell {
2019 sheet, row, col, ..
2020 } => {
2021 let sheet_name = sheet.as_deref().unwrap_or(self.current_sheet);
2022 match self
2023 .context
2024 .resolve_cell_reference(Some(sheet_name), *row, *col)
2025 {
2026 Ok(v) => v,
2027 Err(e) => LiteralValue::Error(e),
2028 }
2029 }
2030 ReferenceType::Range {
2031 sheet,
2032 start_row,
2033 start_col,
2034 end_row,
2035 end_col,
2036 ..
2037 } => {
2038 let sheet_name = sheet.as_deref().unwrap_or(self.current_sheet);
2039
2040 const SHEET_ROWS: u32 = 1_048_576;
2045 const SHEET_COLS: u32 = 16_384;
2046 let (sr, sc, er, ec) = (
2047 start_row.unwrap_or(1),
2048 start_col.unwrap_or(1),
2049 end_row.unwrap_or(SHEET_ROWS),
2050 end_col.unwrap_or(SHEET_COLS),
2051 );
2052
2053 let (mut sr, mut er) = (sr, er);
2055 let (mut sc, mut ec) = (sc, ec);
2056 if sr > er {
2057 std::mem::swap(&mut sr, &mut er);
2058 }
2059 if sc > ec {
2060 std::mem::swap(&mut sc, &mut ec);
2061 }
2062
2063 let pick = if sc == ec {
2064 if cur_r1 < sr || cur_r1 > er {
2066 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
2067 }
2068 (cur_r1, sc)
2069 } else if sr == er {
2070 if cur_c1 < sc || cur_c1 > ec {
2072 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
2073 }
2074 (sr, cur_c1)
2075 } else {
2076 if cur_r1 < sr || cur_r1 > er || cur_c1 < sc || cur_c1 > ec {
2078 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
2079 }
2080 (cur_r1, cur_c1)
2081 };
2082
2083 match self
2084 .context
2085 .resolve_cell_reference(Some(sheet_name), pick.0, pick.1)
2086 {
2087 Ok(v) => v,
2088 Err(e) => LiteralValue::Error(e),
2089 }
2090 }
2091 ReferenceType::External(ext) => {
2094 let formualizer_parse::parser::ExternalRefKind::Range {
2095 start_row: Some(sr),
2096 start_col: Some(sc),
2097 end_row: Some(er),
2098 end_col: Some(ec),
2099 ..
2100 } = ext.kind
2101 else {
2102 return match self.eval_reference_to_calc(reference) {
2103 Ok(cv) => self.eval_implicit_intersection_calc(cv),
2104 Err(e) => LiteralValue::Error(e),
2105 };
2106 };
2107 let (sr, er) = (sr.min(er), sr.max(er));
2108 let (sc, ec) = (sc.min(ec), sc.max(ec));
2109 let (row, col) = if sc == ec {
2110 if cur_r1 < sr || cur_r1 > er {
2111 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
2112 }
2113 (cur_r1, sc)
2114 } else if sr == er {
2115 if cur_c1 < sc || cur_c1 > ec {
2116 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
2117 }
2118 (sr, cur_c1)
2119 } else {
2120 if cur_r1 < sr || cur_r1 > er || cur_c1 < sc || cur_c1 > ec {
2121 return LiteralValue::Error(ExcelError::new(ExcelErrorKind::Value));
2122 }
2123 (cur_r1, cur_c1)
2124 };
2125 match self.eval_reference_to_calc(reference) {
2126 Ok(crate::traits::CalcValue::Range(rv)) => {
2127 rv.get_cell((row - sr) as usize, (col - sc) as usize)
2128 }
2129 Ok(cv) => self.eval_implicit_intersection_calc(cv),
2130 Err(e) => LiteralValue::Error(e),
2131 }
2132 }
2133 other => {
2135 let cv = match self.eval_reference_to_calc(other) {
2136 Ok(cv) => cv,
2137 Err(e) => return LiteralValue::Error(e),
2138 };
2139 self.eval_implicit_intersection_calc(cv)
2140 }
2141 }
2142 }
2143
2144 fn apply_number_unary(&self, v: LiteralValue, op: u8) -> Result<LiteralValue, ExcelError> {
2145 match crate::coercion::to_arithmetic_number_with_locale(
2146 &v,
2147 &self.context.locale(),
2148 self.context.date_system(),
2149 ) {
2150 Ok(n) => match unary_f64(op, n) {
2151 Ok(n2) => Ok(LiteralValue::Number(n2)),
2152 Err(e) => Ok(LiteralValue::Error(e)),
2153 },
2154 Err(e) => Ok(LiteralValue::Error(e)),
2155 }
2156 }
2157
2158 fn eval_binary(
2160 &self,
2161 op: BinaryOperator,
2162 left_node: &ASTNode,
2163 right_node: &ASTNode,
2164 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
2165 let left_calc = self.evaluate_ast_uncached(left_node)?;
2166 let left_format = left_calc.format_id();
2167 let left = left_calc.into_literal();
2168 let right_calc = self.evaluate_ast_uncached(right_node)?;
2169 let right_format = right_calc.format_id();
2170 let right = right_calc.into_literal();
2171 if op == BinaryOperator::Range {
2172 let left_ref = self.evaluate_ast_as_reference(left_node)?;
2173 let right_ref = self.evaluate_ast_as_reference(right_node)?;
2174 return match crate::reference::combine_references(&left_ref, &right_ref) {
2175 Ok(_) => Err(ExcelError::new(ExcelErrorKind::Ref)
2176 .with_message("Reference produced by ':' cannot be used directly as a value")),
2177 Err(error) => Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(error))),
2178 };
2179 }
2180 self.apply_binary_op(op, left, left_format, right, right_format)
2181 }
2182
2183 fn eval_function_to_calc(
2185 &self,
2186 name: &str,
2187 args: &[ASTNode],
2188 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
2189 if let Some(fun) = self.ast_function(name) {
2190 let handles: smallvec::SmallVec<[ArgumentHandle; 4]> =
2191 args.iter().map(|n| ArgumentHandle::new(n, self)).collect();
2192 let fctx = DefaultFunctionContext::new_with_sheet(
2194 self.context,
2195 self.current_cell,
2196 self.current_sheet,
2197 );
2198 return self.diagnostic_dispatch(
2199 name,
2200 &*fun,
2201 fun.reports_known_limitations(),
2202 || fun.dispatch(&handles, &fctx),
2203 crate::operation_diagnostics::is_unimplemented,
2204 );
2205 }
2206
2207 if let Some(callable) = self.resolve_local_callable(name) {
2208 let mut eval_args = Vec::with_capacity(args.len());
2209 let mut arg_references = Vec::with_capacity(args.len());
2210 for arg in args {
2211 arg_references
2215 .push(ArgumentHandle::new(arg, self).bound_reference_in_env(&self.local_env));
2216 eval_args.push(self.evaluate_ast_uncached(arg)?.into_literal());
2217 }
2218 return self.function_call(name, || {
2219 callable.invoke_with_references(self, &eval_args, &arg_references)
2220 });
2221 }
2222
2223 self.unknown_function(name);
2224 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Error(
2226 ExcelError::new(ExcelErrorKind::Name).with_message(format!("Unknown function: {name}")),
2227 )))
2228 }
2229
2230 fn eval_function(&self, name: &str, args: &[ASTNode]) -> Result<LiteralValue, ExcelError> {
2231 self.eval_function_to_calc(name, args)
2232 .map(|cv| cv.into_literal())
2233 }
2234
2235 pub fn function_context(&self, cell_ref: Option<&CellRef>) -> DefaultFunctionContext<'_> {
2236 DefaultFunctionContext::new_with_sheet(self.context, cell_ref.cloned(), self.current_sheet)
2237 }
2238
2239 fn eval_array_literal_to_calc(
2241 &self,
2242 rows: &[Vec<ASTNode>],
2243 ) -> Result<crate::traits::CalcValue<'a>, ExcelError> {
2244 let mut out = Vec::with_capacity(rows.len());
2245 for row in rows {
2246 let mut r = Vec::with_capacity(row.len());
2247 for cell in row {
2248 r.push(self.evaluate_ast_uncached(cell)?.into_literal());
2249 }
2250 out.push(r);
2251 }
2252 Ok(crate::traits::CalcValue::Range(
2253 crate::engine::range_view::RangeView::from_owned_rows(out, self.context.date_system()),
2254 ))
2255 }
2256
2257 fn eval_array_literal(&self, rows: &[Vec<ASTNode>]) -> Result<LiteralValue, ExcelError> {
2258 self.eval_array_literal_to_calc(rows)
2259 .map(|cv| cv.into_literal())
2260 }
2261
2262 fn numeric_binary(
2266 &self,
2267 left: LiteralValue,
2268 right: LiteralValue,
2269 op: u8,
2270 ) -> Result<LiteralValue, ExcelError> {
2271 self.broadcast_apply(left, right, |l, r| {
2272 let a = crate::coercion::to_arithmetic_number_with_locale(
2273 &l,
2274 &self.context.locale(),
2275 self.context.date_system(),
2276 );
2277 let b = crate::coercion::to_arithmetic_number_with_locale(
2278 &r,
2279 &self.context.locale(),
2280 self.context.date_system(),
2281 );
2282 match (a, b) {
2283 (Ok(a), Ok(b)) => match arith_f64(op, a, b) {
2284 Ok(n) => Ok(LiteralValue::Number(n)),
2285 Err(e) => Ok(LiteralValue::Error(e)),
2286 },
2287 (Err(e), _) | (_, Err(e)) => Ok(LiteralValue::Error(e)),
2288 }
2289 })
2290 }
2291
2292 fn concat_values(
2296 &self,
2297 left: LiteralValue,
2298 right: LiteralValue,
2299 ) -> Result<LiteralValue, ExcelError> {
2300 fn concat_scalar(
2301 left: LiteralValue,
2302 right: LiteralValue,
2303 ) -> Result<LiteralValue, ExcelError> {
2304 Ok(match (left, right) {
2305 (LiteralValue::Error(error), _) | (_, LiteralValue::Error(error)) => {
2306 LiteralValue::Error(error)
2307 }
2308 (left, right) => LiteralValue::Text(format!(
2309 "{}{}",
2310 crate::coercion::to_text_invariant(&left),
2311 crate::coercion::to_text_invariant(&right)
2312 )),
2313 })
2314 }
2315 if matches!(left, LiteralValue::Array(_)) || matches!(right, LiteralValue::Array(_)) {
2317 self.broadcast_apply(left, right, concat_scalar)
2318 } else {
2319 concat_scalar(left, right)
2320 }
2321 }
2322
2323 fn map_array<F>(&self, arr: Vec<Vec<LiteralValue>>, f: F) -> Result<LiteralValue, ExcelError>
2324 where
2325 F: Fn(LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
2326 {
2327 let mut out = Vec::with_capacity(arr.len());
2328 for row in arr {
2329 let mut new_row = Vec::with_capacity(row.len());
2330 for cell in row {
2331 new_row.push(match f(cell) {
2332 Ok(v) => v,
2333 Err(e) => LiteralValue::Error(e),
2334 });
2335 }
2336 out.push(new_row);
2337 }
2338 Ok(LiteralValue::Array(out))
2339 }
2340
2341 fn combine_arrays<F>(
2342 &self,
2343 l: Vec<Vec<LiteralValue>>,
2344 r: Vec<Vec<LiteralValue>>,
2345 f: F,
2346 ) -> Result<LiteralValue, ExcelError>
2347 where
2348 F: Fn(LiteralValue, LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
2349 {
2350 let l_shape = (l.len(), l.first().map(|r| r.len()).unwrap_or(0));
2352 let r_shape = (r.len(), r.first().map(|r| r.len()).unwrap_or(0));
2353 let target = match broadcast_shape(&[l_shape, r_shape]) {
2354 Ok(s) => s,
2355 Err(e) => return Ok(LiteralValue::Error(e)),
2356 };
2357 if let Some(e) = crate::engine::resource_ledger::materialized_cells_exceed(
2360 target.0,
2361 target.1,
2362 self.context.materialized_array_cells(),
2363 ) {
2364 return Ok(LiteralValue::Error(e));
2365 }
2366
2367 let mut out = Vec::with_capacity(target.0);
2368 for i in 0..target.0 {
2369 let mut row = Vec::with_capacity(target.1);
2370 for j in 0..target.1 {
2371 let (li, lj) = project_index((i, j), l_shape);
2372 let (ri, rj) = project_index((i, j), r_shape);
2373 let lv = l
2374 .get(li)
2375 .and_then(|r| r.get(lj))
2376 .cloned()
2377 .unwrap_or(LiteralValue::Empty);
2378 let rv = r
2379 .get(ri)
2380 .and_then(|r| r.get(rj))
2381 .cloned()
2382 .unwrap_or(LiteralValue::Empty);
2383 row.push(match f(lv, rv) {
2384 Ok(v) => v,
2385 Err(e) => LiteralValue::Error(e),
2386 });
2387 }
2388 out.push(row);
2389 }
2390 Ok(LiteralValue::Array(out))
2391 }
2392
2393 fn broadcast_apply<F>(
2394 &self,
2395 left: LiteralValue,
2396 right: LiteralValue,
2397 f: F,
2398 ) -> Result<LiteralValue, ExcelError>
2399 where
2400 F: Fn(LiteralValue, LiteralValue) -> Result<LiteralValue, ExcelError> + Copy,
2401 {
2402 use LiteralValue::*;
2403 match (left, right) {
2404 (Array(l), Array(r)) => self.combine_arrays(l, r, f),
2405 (Array(arr), v) => {
2406 let shape_l = (arr.len(), arr.first().map(|r| r.len()).unwrap_or(0));
2407 let shape_r = (1usize, 1usize);
2408 let target = match broadcast_shape(&[shape_l, shape_r]) {
2409 Ok(s) => s,
2410 Err(e) => return Ok(LiteralValue::Error(e)),
2411 };
2412 let mut out = Vec::with_capacity(target.0);
2413 for i in 0..target.0 {
2414 let mut row = Vec::with_capacity(target.1);
2415 for j in 0..target.1 {
2416 let (li, lj) = project_index((i, j), shape_l);
2417 let lv = arr
2418 .get(li)
2419 .and_then(|r| r.get(lj))
2420 .cloned()
2421 .unwrap_or(LiteralValue::Empty);
2422 row.push(match f(lv, v.clone()) {
2423 Ok(vv) => vv,
2424 Err(e) => LiteralValue::Error(e),
2425 });
2426 }
2427 out.push(row);
2428 }
2429 Ok(LiteralValue::Array(out))
2430 }
2431 (v, Array(arr)) => {
2432 let shape_l = (1usize, 1usize);
2433 let shape_r = (arr.len(), arr.first().map(|r| r.len()).unwrap_or(0));
2434 let target = match broadcast_shape(&[shape_l, shape_r]) {
2435 Ok(s) => s,
2436 Err(e) => return Ok(LiteralValue::Error(e)),
2437 };
2438 let mut out = Vec::with_capacity(target.0);
2439 for i in 0..target.0 {
2440 let mut row = Vec::with_capacity(target.1);
2441 for j in 0..target.1 {
2442 let (ri, rj) = project_index((i, j), shape_r);
2443 let rv = arr
2444 .get(ri)
2445 .and_then(|r| r.get(rj))
2446 .cloned()
2447 .unwrap_or(LiteralValue::Empty);
2448 row.push(match f(v.clone(), rv) {
2449 Ok(vv) => vv,
2450 Err(e) => LiteralValue::Error(e),
2451 });
2452 }
2453 out.push(row);
2454 }
2455 Ok(LiteralValue::Array(out))
2456 }
2457 (l, r) => f(l, r),
2458 }
2459 }
2460
2461 fn coerce_number(&self, v: &LiteralValue) -> Result<f64, ExcelError> {
2463 coercion::to_number_lenient(v)
2464 }
2465
2466 fn coerce_text(&self, v: &LiteralValue) -> String {
2467 coercion::to_text_invariant(v)
2468 }
2469
2470 fn compare(
2472 &self,
2473 op: BinaryOperator,
2474 left: LiteralValue,
2475 right: LiteralValue,
2476 ) -> Result<LiteralValue, ExcelError> {
2477 use LiteralValue::*;
2478 if matches!(left, Error(_)) {
2479 return Ok(left);
2480 }
2481 if matches!(right, Error(_)) {
2482 return Ok(right);
2483 }
2484
2485 match (left, right) {
2487 (Array(l), Array(r)) => self.combine_arrays(l, r, |a, b| self.compare(op, a, b)),
2488 (Array(arr), v) => self.broadcast_apply(Array(arr), v, |a, b| self.compare(op, a, b)),
2489 (v, Array(arr)) => self.broadcast_apply(v, Array(arr), |a, b| self.compare(op, a, b)),
2490 (l, r) => {
2491 let res = match (l, r) {
2492 (Number(a), Number(b)) => self.cmp_f64(a, b, op),
2495 (Int(a), Int(b)) => self.cmp_f64(a as f64, b as f64, op),
2496 (Int(a), Number(b)) => self.cmp_f64(a as f64, b, op),
2497 (Number(a), Int(b)) => self.cmp_f64(a, b as f64, op),
2498 (Boolean(a), Boolean(b)) => {
2499 self.cmp_f64(if a { 1.0 } else { 0.0 }, if b { 1.0 } else { 0.0 }, op)
2500 }
2501 (Text(a), Text(b)) => self.cmp_text(&a, &b, op),
2502 (a, b) => self.cmp_ranked(a, b, op),
2503 };
2504 Ok(LiteralValue::Boolean(res))
2505 }
2506 }
2507 }
2508
2509 fn cmp_ranked(&self, l: LiteralValue, r: LiteralValue, op: BinaryOperator) -> bool {
2525 use LiteralValue::*;
2526
2527 let (l, r) = match (l, r) {
2531 (Empty, Empty) => (Number(0.0), Number(0.0)),
2532 (Empty, r) => (empty_as_zero_of(&r), r),
2533 (l, Empty) => {
2534 let e = empty_as_zero_of(&l);
2535 (l, e)
2536 }
2537 (l, r) => (l, r),
2538 };
2539
2540 let (lr, rr) = (excel_type_rank(&l), excel_type_rank(&r));
2541 if lr != rr {
2542 return self.cmp_f64(lr as f64, rr as f64, op);
2543 }
2544
2545 match (l, r) {
2546 (Text(a), Text(b)) => self.cmp_text(&a, &b, op),
2547 (Boolean(a), Boolean(b)) => {
2548 self.cmp_f64(if a { 1.0 } else { 0.0 }, if b { 1.0 } else { 0.0 }, op)
2549 }
2550 (a, b) => {
2553 let an = crate::coercion::to_number_strict(&a).ok();
2554 let bn = crate::coercion::to_number_strict(&b).ok();
2555 if let (Some(a), Some(b)) = (an, bn) {
2556 self.cmp_f64(a, b, op)
2557 } else {
2558 self.cmp_text(
2562 &crate::coercion::to_text_invariant(&a),
2563 &crate::coercion::to_text_invariant(&b),
2564 op,
2565 )
2566 }
2567 }
2568 }
2569 }
2570
2571 fn cmp_f64(&self, a: f64, b: f64, op: BinaryOperator) -> bool {
2572 cmp_f64(a, b, op)
2573 }
2574 fn cmp_text(&self, a: &str, b: &str, op: BinaryOperator) -> bool {
2575 let loc = self.context.locale();
2576 let (a, b) = (loc.fold_case_invariant(a), loc.fold_case_invariant(b));
2577 self.cmp_f64(a.cmp(&b) as i32 as f64, 0.0, op)
2578 }
2579}
2580
2581#[inline]
2584pub(crate) fn cmp_f64(a: f64, b: f64, op: BinaryOperator) -> bool {
2585 match op {
2586 BinaryOperator::Equal => a == b,
2587 BinaryOperator::NotEqual => a != b,
2588 BinaryOperator::Greater => a > b,
2589 BinaryOperator::Less => a < b,
2590 BinaryOperator::GreaterEqual => a >= b,
2591 BinaryOperator::LessEqual => a <= b,
2592 _ => unreachable!(),
2593 }
2594}
2595
2596pub(crate) fn excel_pow(base: f64, exponent: f64) -> Result<f64, ExcelError> {
2605 if base < 0.0 && exponent.fract() != 0.0 {
2606 let root = 1.0 / exponent;
2607 let root = format!("{root:.14e}").parse::<f64>().unwrap_or(f64::NAN);
2609 let odd = root.is_finite() && root.rem_euclid(2.0) == 1.0;
2610 if !odd {
2611 return Err(ExcelError::new_num());
2612 }
2613 return Ok(-(-base).powf(exponent));
2614 }
2615 Ok(base.powf(exponent))
2616}
2617
2618#[inline]
2624pub(crate) fn arith_f64(op: u8, a: f64, b: f64) -> Result<f64, ExcelError> {
2625 let v = match op {
2626 b'+' => a + b,
2627 b'-' => a - b,
2628 b'*' => a * b,
2629 b'/' => {
2630 if b == 0.0 {
2631 return Err(ExcelError::from_error_string("#DIV/0!"));
2632 }
2633 a / b
2634 }
2635 b'^' => excel_pow(a, b)?,
2636 _ => unreachable!("arithmetic operator"),
2637 };
2638 crate::coercion::sanitize_numeric(v)
2639}
2640
2641#[inline]
2644pub(crate) fn unary_f64(op: u8, n: f64) -> Result<f64, ExcelError> {
2645 crate::coercion::sanitize_numeric(match op {
2646 b'-' => -n,
2647 b'%' => n / 100.0,
2648 _ => unreachable!("unary operator"),
2649 })
2650}
2651
2652fn excel_type_rank(v: &LiteralValue) -> u8 {
2660 match v {
2661 LiteralValue::Text(_) => 1,
2662 LiteralValue::Boolean(_) => 2,
2663 _ => 0,
2664 }
2665}
2666
2667fn empty_as_zero_of(other: &LiteralValue) -> LiteralValue {
2671 match other {
2672 LiteralValue::Text(_) => LiteralValue::Text(String::new()),
2673 LiteralValue::Boolean(_) => LiteralValue::Boolean(false),
2674 _ => LiteralValue::Number(0.0),
2675 }
2676}
2677
2678fn relocate_reference_for_offset(
2679 reference: &ReferenceType,
2680 row_delta: i64,
2681 col_delta: i64,
2682) -> Result<ReferenceType, ExcelError> {
2683 match reference {
2684 ReferenceType::Cell {
2685 sheet,
2686 row,
2687 col,
2688 row_abs,
2689 col_abs,
2690 } => Ok(ReferenceType::Cell {
2691 sheet: sheet.clone(),
2692 row: shift_axis_for_offset(*row, row_delta, *row_abs)?,
2693 col: shift_axis_for_offset(*col, col_delta, *col_abs)?,
2694 row_abs: *row_abs,
2695 col_abs: *col_abs,
2696 }),
2697 ReferenceType::Range {
2698 sheet,
2699 start_row,
2700 start_col,
2701 end_row,
2702 end_col,
2703 start_row_abs,
2704 start_col_abs,
2705 end_row_abs,
2706 end_col_abs,
2707 } => Ok(ReferenceType::Range {
2708 sheet: sheet.clone(),
2709 start_row: shift_optional_axis_for_offset(*start_row, row_delta, *start_row_abs)?,
2710 start_col: shift_optional_axis_for_offset(*start_col, col_delta, *start_col_abs)?,
2711 end_row: shift_optional_axis_for_offset(*end_row, row_delta, *end_row_abs)?,
2712 end_col: shift_optional_axis_for_offset(*end_col, col_delta, *end_col_abs)?,
2713 start_row_abs: *start_row_abs,
2714 start_col_abs: *start_col_abs,
2715 end_row_abs: *end_row_abs,
2716 end_col_abs: *end_col_abs,
2717 }),
2718 ReferenceType::NamedRange(name) => Ok(ReferenceType::NamedRange(name.clone())),
2721 ReferenceType::Table(_)
2722 | ReferenceType::Cell3D { .. }
2723 | ReferenceType::Range3D { .. }
2724 | ReferenceType::External(_) => Err(unsupported_reference_relocation_error()),
2725 }
2726}
2727
2728fn shift_optional_axis_for_offset(
2729 value: Option<u32>,
2730 delta: i64,
2731 is_absolute: bool,
2732) -> Result<Option<u32>, ExcelError> {
2733 value
2734 .map(|value| shift_axis_for_offset(value, delta, is_absolute))
2735 .transpose()
2736}
2737
2738pub(crate) fn shift_axis_for_offset(
2739 value: u32,
2740 delta: i64,
2741 is_absolute: bool,
2742) -> Result<u32, ExcelError> {
2743 if is_absolute {
2744 return Ok(value);
2745 }
2746 let shifted = i64::from(value) + delta;
2747 if shifted < 1 || shifted > i64::from(u32::MAX) {
2748 return Err(unsupported_reference_relocation_error());
2749 }
2750 Ok(shifted as u32)
2751}
2752
2753fn unsupported_reference_relocation_error() -> ExcelError {
2754 ExcelError::new(ExcelErrorKind::Ref)
2755 .with_message("Unsupported reference relocation for FormulaPlane span evaluation")
2756}
2757
2758#[cfg(test)]
2759mod format_algebra_tests {
2760 use super::*;
2761
2762 #[test]
2763 fn compact_argument_ranges_match_owned_resolution() -> Result<(), Box<dyn std::error::Error>> {
2764 use crate::traits::ResolvedArgument;
2765 let mut config = EvalConfig::default();
2766 config.evaluation_budgets.scratch.materialized_array_cells = Some(4);
2767 let mut engine = Engine::new(TestWorkbook::new(), config);
2768 let sheet_id = engine.add_sheet("Other")?;
2769 engine.set_cell_value("Other", 2, 2, LiteralValue::Number(17.0))?;
2770 engine.set_cell_value("Other", 4, 3, LiteralValue::Text("end".into()))?;
2771 let template = formualizer_parse::parser::parse("=A1:C3")?;
2772 let cell = crate::CellRef::new(
2773 sheet_id,
2774 crate::reference::Coord::from_excel(1, 1, true, true),
2775 );
2776 for qualified in [false, true] {
2777 for open_rows in [false, true] {
2778 for open_cols in [false, true] {
2779 for absolute in [false, true] {
2780 for delta in [-2, 0, 2] {
2781 let reference = ReferenceType::Range {
2782 sheet: qualified.then(|| "Other".to_string()),
2783 start_row: (!open_rows).then_some(1),
2784 start_col: (!open_cols).then_some(1),
2785 end_row: (!open_rows).then_some(3),
2786 end_col: (!open_cols).then_some(3),
2787 start_row_abs: absolute,
2788 start_col_abs: absolute,
2789 end_row_abs: absolute,
2790 end_col_abs: absolute,
2791 };
2792 let mut ast = template.clone();
2793 ast.node_type = ASTNodeType::Reference {
2794 original: reference.to_string(),
2795 reference,
2796 };
2797 let mut store = DataStore::new();
2798 let registry = engine.graph.sheet_reg();
2799 let id = store.store_ast(&ast, registry);
2800 let mut slow =
2801 Interpreter::new(&engine, "Other").with_current_cell(cell);
2802 slow.reference_row_delta = delta;
2803 slow.reference_col_delta = delta;
2804 let fast = slow.with_current_cell(cell).with_arena_cells(&engine);
2805 assert!(fast.resolve_arena_argument_range(id, &store).is_some());
2806 assert!(slow.resolve_arena_argument_range(id, &store).is_none());
2807 let a = ArgumentHandle::new_arena(id, &fast, &store, registry)
2808 .resolve_once();
2809 let b = ArgumentHandle::new_arena(id, &slow, &store, registry)
2810 .resolve_once();
2811 match (a, b) {
2812 (
2813 Ok(ResolvedArgument::Range(a)),
2814 Ok(ResolvedArgument::Range(b)),
2815 ) => {
2816 assert_eq!(a.dims(), b.dims());
2817 assert_eq!(
2818 (a.start_row(), a.start_col()),
2819 (b.start_row(), b.start_col())
2820 );
2821 assert_eq!(
2822 a.materialization_error().map(|e| e.kind),
2823 b.materialization_error().map(|e| e.kind)
2824 );
2825 let (rows, cols) = a.dims();
2826 for row in 0..rows.min(8) {
2827 for col in 0..cols.min(8) {
2828 assert_eq!(a.get_cell(row, col), b.get_cell(row, col));
2829 }
2830 }
2831 }
2832 (
2833 Ok(ResolvedArgument::ReferenceError(a)),
2834 Ok(ResolvedArgument::ReferenceError(b)),
2835 ) => assert_eq!(a, b),
2836 (Err(a), Err(b)) => assert_eq!(a, b),
2837 (a, b) => assert_eq!(format!("{a:?}"), format!("{b:?}")),
2838 }
2839 }
2840 }
2841 }
2842 }
2843 }
2844 Ok(())
2845 }
2846
2847 #[test]
2848 fn compact_ranges_match_owned_resolution_after_relocation() -> Result<(), ExcelError> {
2849 let mut engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
2850 let sheet_id = engine.add_sheet("Other")?;
2851 engine.set_cell_value("Other", 2, 2, LiteralValue::Number(17.0))?;
2852 engine.set_cell_value("Other", 4, 3, LiteralValue::Text("end".into()))?;
2853 for qualified in [false, true] {
2854 for open_rows in [false, true] {
2855 for open_cols in [false, true] {
2856 for absolute in [false, true] {
2857 for delta in [-2, 0, 2] {
2858 let compact = CompactRefType::Range {
2859 sheet: qualified.then_some(SheetKey::Id(sheet_id)),
2860 start_row: if open_rows { 0 } else { 1 },
2861 start_col: if open_cols { 0 } else { 1 },
2862 end_row: if open_rows { u32::MAX } else { 3 },
2863 end_col: if open_cols { u32::MAX } else { 3 },
2864 start_row_abs: absolute,
2865 start_col_abs: absolute,
2866 end_row_abs: absolute,
2867 end_col_abs: absolute,
2868 };
2869 let owned = ReferenceType::Range {
2870 sheet: qualified.then(|| "Other".to_string()),
2871 start_row: (!open_rows).then_some(1),
2872 start_col: (!open_cols).then_some(1),
2873 end_row: (!open_rows).then_some(3),
2874 end_col: (!open_cols).then_some(3),
2875 start_row_abs: absolute,
2876 start_col_abs: absolute,
2877 end_row_abs: absolute,
2878 end_col_abs: absolute,
2879 };
2880 let expected = relocate_reference_for_offset(&owned, delta, delta)
2881 .and_then(|r| {
2882 crate::traits::EvaluationContext::resolve_range_view(
2883 &engine, &r, "Other",
2884 )
2885 });
2886 let actual =
2887 engine.resolve_arena_range(&compact, sheet_id, delta, delta);
2888 match (actual, expected) {
2889 (Ok(a), Ok(b)) => {
2890 assert_eq!(a.dims(), b.dims());
2891 assert_eq!(
2892 (a.start_row(), a.start_col()),
2893 (b.start_row(), b.start_col())
2894 );
2895 let (rows, cols) = a.dims();
2896 for row in 0..rows.min(8) {
2897 for col in 0..cols.min(8) {
2898 assert_eq!(a.get_cell(row, col), b.get_cell(row, col));
2899 }
2900 }
2901 }
2902 (Err(a), Err(b)) => assert_eq!(a.kind, b.kind),
2903 (a, b) => assert_eq!(format!("{a:?}"), format!("{b:?}")),
2904 }
2905 }
2906 }
2907 }
2908 }
2909 }
2910 engine.rename_sheet(sheet_id, "Renamed")?;
2911 assert!(
2912 engine
2913 .arrow_sheet_by_id(sheet_id)
2914 .is_some_and(|s| s.name.as_ref() == "Renamed")
2915 );
2916 assert!(engine.arrow_sheet_by_id_mut(sheet_id).is_some());
2917 let other = engine.add_sheet("Third")?;
2918 engine.sheet_store_mut().sheets.reverse();
2919 assert!(
2920 engine
2921 .arrow_sheet_by_id(sheet_id)
2922 .is_some_and(|s| s.name.as_ref() == "Renamed")
2923 );
2924 engine.remove_sheet(other)?;
2925 assert!(engine.arrow_sheet_by_id(sheet_id).is_some());
2926 Ok(())
2927 }
2928
2929 use crate::engine::{EvalConfig, eval::Engine};
2930 use crate::format::FormatId;
2931 use crate::test_workbook::TestWorkbook;
2932
2933 #[test]
2934 fn binary_fastpath_preserves_values_and_format_annotations() {
2935 let engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
2936 let interpreter = Interpreter::new(&engine, "Sheet1");
2937 let values = [
2938 LiteralValue::Number(2.5),
2939 LiteralValue::Int(2),
2940 LiteralValue::Text("3".into()),
2941 LiteralValue::Text("not numeric".into()),
2942 LiteralValue::Boolean(true),
2943 LiteralValue::Empty,
2944 LiteralValue::Error(ExcelError::new(ExcelErrorKind::Na)),
2945 LiteralValue::Array(vec![vec![LiteralValue::Number(1.0)]]),
2946 ];
2947 let formats = [
2948 None,
2949 Some(FormatId::DATE),
2950 Some(FormatId::TIME),
2951 Some(FormatId(49)),
2952 ];
2953 for (op, byte) in [
2954 (BinaryOperator::Add, b'+'),
2955 (BinaryOperator::Subtract, b'-'),
2956 ] {
2957 for left in &values {
2958 for right in &values {
2959 for left_format in formats {
2960 for right_format in formats {
2961 let expected = interpreter
2962 .numeric_binary(left.clone(), right.clone(), byte)
2963 .map(|value| {
2964 interpreter.annotate_numeric_result(
2965 value,
2966 interpreter.binary_format(
2967 char::from(byte),
2968 left_format,
2969 right_format,
2970 ),
2971 )
2972 });
2973 let actual = interpreter.apply_binary_op(
2974 op,
2975 left.clone(),
2976 left_format,
2977 right.clone(),
2978 right_format,
2979 );
2980 match (actual, expected) {
2981 (Ok(actual), Ok(expected)) => {
2982 assert_eq!(actual.format_id(), expected.format_id());
2983 assert_eq!(actual.into_literal(), expected.into_literal());
2984 }
2985 (Err(actual), Err(expected)) => assert_eq!(actual, expected),
2986 _ => panic!("operator result kind changed"),
2987 }
2988 }
2989 }
2990 }
2991 }
2992 }
2993 }
2994
2995 #[test]
2996 fn temporal_binary_format_algebra_pins_positive_and_negative_cases() {
2997 let engine = Engine::new(TestWorkbook::new(), EvalConfig::default());
2998 let interpreter = Interpreter::new(&engine, "Sheet1");
2999
3000 assert_eq!(
3001 interpreter.binary_format('+', Some(FormatId::DATE), Some(FormatId::TIME)),
3002 Some(FormatId::DATETIME)
3003 );
3004 assert_eq!(
3005 interpreter.binary_format('+', Some(FormatId::DATE), Some(FormatId(9))),
3006 Some(FormatId::DATE),
3007 "Date + Percent follows the measured temporal-wins rule"
3008 );
3009 assert_eq!(
3010 interpreter.binary_format('-', Some(FormatId::DATE), Some(FormatId::DATE)),
3011 None,
3012 "Date - Date is an unformatted duration in days"
3013 );
3014 assert_eq!(
3015 interpreter.binary_format('+', Some(FormatId::DATE), Some(FormatId(49))),
3016 None,
3017 "Date + Text must not acquire a temporal annotation"
3018 );
3019 for (left, right) in [
3020 (FormatId::DATE, FormatId::DATE),
3021 (FormatId::DURATION, FormatId::DATE),
3022 (FormatId::DATE, FormatId(5)),
3023 (FormatId::DATETIME, FormatId::TIME),
3024 ] {
3025 assert_eq!(
3026 interpreter.binary_format('+', Some(left), Some(right)),
3027 None
3028 );
3029 }
3030 }
3031}
3032
3033#[cfg(test)]
3034mod resolved_function_tests {
3035 use super::*;
3036 use crate::function::Function;
3037 use crate::traits::FunctionProvider;
3038 use std::sync::atomic::{AtomicU64, AtomicUsize, Ordering};
3039 use std::sync::{Arc, RwLock};
3040
3041 struct TestFunction;
3042 impl Function for TestFunction {
3043 fn name(&self) -> &'static str {
3044 "FN_TABLE_GLOBAL_REFRESH_TEST"
3045 }
3046 fn eval<'a, 'b, 'c>(
3047 &self,
3048 _: &'c [ArgumentHandle<'a, 'b>],
3049 _: &dyn crate::traits::FunctionContext<'b>,
3050 ) -> Result<crate::traits::CalcValue<'b>, ExcelError> {
3051 Ok(crate::traits::CalcValue::Scalar(LiteralValue::Number(1.0)))
3052 }
3053 }
3054 #[derive(Default)]
3055 struct Provider {
3056 generation: AtomicU64,
3057 calls: AtomicUsize,
3058 function: RwLock<Option<Arc<dyn Function>>>,
3059 }
3060 impl FunctionProvider for Provider {
3061 fn planning_semantic_revision(&self) -> Option<u64> {
3062 Some(self.generation.load(Ordering::Acquire))
3063 }
3064 fn function_capabilities(&self, _: &str, _: &str) -> Option<crate::function::FnCaps> {
3065 self.function.read().unwrap().as_ref().map(|f| f.caps())
3066 }
3067 fn get_function(&self, _: &str, _: &str) -> Option<Arc<dyn Function>> {
3068 self.calls.fetch_add(1, Ordering::Relaxed);
3069 self.function.read().unwrap().clone()
3070 }
3071 }
3072 fn arena(name: &str) -> (DataStore, crate::engine::arena::StringId) {
3073 let mut store = DataStore::new();
3074 let registry = SheetRegistry::new();
3075 let ast = ASTNode::new(
3076 ASTNodeType::Function {
3077 name: name.into(),
3078 args: vec![],
3079 },
3080 None,
3081 );
3082 let node = store.store_ast(&ast, ®istry);
3083 let Some(AstNodeData::Function { name_id, .. }) = store.get_node(node) else {
3084 panic!("function node")
3085 };
3086 let id = *name_id;
3087 (store, id)
3088 }
3089 #[test]
3090 fn function_table_tracks_distinct_names_not_interner_size() {
3091 let mut store = DataStore::new();
3092 let registry = SheetRegistry::new();
3093 for row in 1..=200_000 {
3094 store.store_ast(
3095 &formualizer_parse::parser::parse(format!("=A{row}+1")).unwrap(),
3096 ®istry,
3097 );
3098 }
3099 store.store_ast(
3100 &formualizer_parse::parser::parse("=ABS(1)").unwrap(),
3101 ®istry,
3102 );
3103 let mut table = ResolvedFunctions::default();
3104 table.refresh(&store, &Provider::default());
3105 assert_eq!(table.functions.len(), 1);
3106 assert_eq!(table.capabilities.len(), 1);
3107 assert_eq!(table.slots.len(), 1);
3108 }
3109
3110 #[test]
3111 fn formula_edits_preserve_existing_function_slots() {
3112 let provider = Provider::default();
3113 let (mut store, id) = arena("EXISTING");
3114 let mut table = ResolvedFunctions::default();
3115 table.refresh(&store, &provider);
3116 let slot = table.slots[&id];
3117 let revision = table.revision;
3118 store.store_ast(
3119 &formualizer_parse::parser::parse("=EXISTING()+1").unwrap(),
3120 &SheetRegistry::new(),
3121 );
3122 table.refresh(&store, &provider);
3123 if table.revision == revision {
3126 assert_eq!(provider.calls.load(Ordering::Relaxed), 1);
3127 }
3128 let previous_calls = provider.calls.load(Ordering::Relaxed);
3129 let previous_revision = table.revision;
3130 store.store_ast(
3131 &formualizer_parse::parser::parse("=NEW_NAME()").unwrap(),
3132 &SheetRegistry::new(),
3133 );
3134 table.refresh(&store, &provider);
3135 if table.revision == previous_revision {
3136 assert_eq!(provider.calls.load(Ordering::Relaxed), previous_calls + 1);
3137 }
3138 if table.revision == revision {
3139 assert_eq!(table.slots[&id], slot);
3140 }
3141 assert_eq!(table.functions.len(), 2);
3142 }
3143
3144 #[test]
3145 fn unversioned_provider_is_resolved_fresh_each_request() {
3146 struct Unversioned(Provider);
3147 impl FunctionProvider for Unversioned {
3148 fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
3149 self.0.get_function(ns, name)
3150 }
3151 }
3152 let provider = Unversioned(Provider::default());
3153 let (store, id) = arena("CHANGING");
3154 let mut table = ResolvedFunctions::default();
3155 table.refresh(&store, &provider);
3156 assert!(table.get(id).is_none());
3157 *provider.0.function.write().unwrap() = Some(Arc::new(TestFunction));
3158 table.refresh(&store, &provider);
3159 assert!(table.get(id).is_some());
3160 let previous = table.functions[0].clone().unwrap();
3161 *provider.0.function.write().unwrap() = Some(Arc::new(TestFunction));
3162 table.refresh(&store, &provider);
3163 assert!(!Arc::ptr_eq(
3164 &previous,
3165 table.functions[0].as_ref().unwrap()
3166 ));
3167 }
3168
3169 #[test]
3170 fn hits_and_misses_are_read_only_and_local_generations_refresh() {
3171 let provider = Provider::default();
3172 let (store, id) = arena("MISSING_THEN_REGISTERED");
3173 let mut table = ResolvedFunctions::default();
3174 table.refresh(&store, &provider);
3175 for _ in 0..10 {
3176 assert!(table.get(id).is_none());
3177 }
3178 assert_eq!(provider.calls.load(Ordering::Relaxed), 1);
3179 *provider.function.write().unwrap() = Some(Arc::new(TestFunction));
3180 provider.generation.fetch_add(1, Ordering::Release);
3181 assert!(table.get(id).is_none());
3183 table.refresh(&store, &provider);
3184 assert!(table.get(id).is_some());
3185 let calls = provider.calls.load(Ordering::Relaxed);
3186 for _ in 0..10 {
3187 assert!(table.get(id).is_some());
3188 }
3189 assert_eq!(provider.calls.load(Ordering::Relaxed), calls);
3190 *provider.function.write().unwrap() = None;
3191 provider.generation.fetch_add(1, Ordering::Release);
3192 assert!(table.get(id).is_some());
3193 table.refresh(&store, &provider);
3194 assert!(table.get(id).is_none());
3195 }
3196 #[test]
3197 fn global_epoch_and_excel_prefix_aliases_refresh() {
3198 struct Global;
3199 impl FunctionProvider for Global {
3200 fn get_function(&self, ns: &str, name: &str) -> Option<Arc<dyn Function>> {
3201 crate::function_registry::get(ns, name)
3202 }
3203 }
3204 let (store, id) = arena("_xlfn.FN_TABLE_GLOBAL_REFRESH_TEST");
3205 let mut table = ResolvedFunctions::default();
3206 table.refresh(&store, &Global);
3207 assert!(table.get(id).is_none());
3208 crate::function_registry::register_function(Arc::new(TestFunction));
3209 table.refresh(&store, &Global);
3210 assert_eq!(
3211 table.get(id).unwrap().name(),
3212 "FN_TABLE_GLOBAL_REFRESH_TEST"
3213 );
3214 let previous = table
3215 .functions
3216 .get(*table.slots.get(&id).unwrap())
3217 .unwrap()
3218 .as_ref()
3219 .unwrap()
3220 .clone();
3221 crate::function_registry::register_function(Arc::new(TestFunction));
3222 table.refresh(&store, &Global);
3223 let replacement = table
3224 .functions
3225 .get(*table.slots.get(&id).unwrap())
3226 .unwrap()
3227 .as_ref()
3228 .unwrap();
3229 assert!(!Arc::ptr_eq(&previous, replacement));
3230 }
3231}