1mod edit;
5mod functions;
6
7pub use edit::get_word_boundaries_from_str;
8
9use serde::{Deserialize, Serialize};
10use std::collections::{HashMap, HashSet, VecDeque};
11use web_time::Instant;
12
13use super::cell::{CellRef, Dependency, EngineError, EvalError, TextCellRef, generate_unique_id};
14use super::column::DataColumn;
15use super::result_data::ResultData;
16#[derive(Default)]
18pub struct Context<'a> {
19 pub sheets: HashMap<String, &'a Sheet>,
21 pub pivot_tables: &'a [crate::core::pivot::PivotTable],
27 pub sheet_order: Vec<String>,
33}
34
35impl<'a> Context<'a> {
36 pub fn new() -> Self {
38 Self {
39 sheets: HashMap::new(),
40 pivot_tables: &[],
41 sheet_order: Vec::new(),
42 }
43 }
44
45 pub fn add_table(&mut self, name: String, sheet: &'a Sheet) {
47 self.sheets.insert(name, sheet);
48 }
49}
50
51enum LetScope<'a> {
59 Empty,
60 Bound {
61 name: &'a str,
62 value: &'a ResultData,
63 parent: &'a LetScope<'a>,
64 },
65}
66
67impl<'a> LetScope<'a> {
68 fn get(&self, name: &str) -> Option<&ResultData> {
69 match self {
70 LetScope::Empty => None,
71 LetScope::Bound {
72 name: n,
73 value,
74 parent,
75 } => {
76 if n.eq_ignore_ascii_case(name) {
77 Some(value)
78 } else {
79 parent.get(name)
80 }
81 }
82 }
83 }
84}
85
86#[derive(Debug, Clone, Copy, PartialEq)]
88pub enum Direction {
89 None,
91 Up,
93 Down,
95 Left,
97 Right,
99}
100
101#[derive(Debug, Clone, Serialize, Deserialize)]
136pub struct Sheet {
137 #[serde(default = "generate_unique_id")]
140 pub id: u64,
141 pub name: String,
143 pub(crate) columns: Vec<DataColumn>,
150 #[serde(default)]
152 pub tables: Vec<crate::core::table::ExcelTable>,
153 #[serde(skip, default)]
156 pub dependencies: HashMap<Dependency, HashSet<CellRef>>,
157 #[serde(skip, default)]
160 pub dependencies_rev: HashMap<CellRef, HashSet<Dependency>>,
161 #[serde(skip)]
164 pub uncommitted_actions: Vec<crate::core::SheetAction>,
165}
166
167#[derive(Debug, Clone, Serialize, Deserialize)]
170pub struct SheetInit {
171 #[serde(default)]
173 pub id: Option<u64>,
174 pub name: Option<String>,
176 pub rows: usize,
178 pub cols: usize,
180}
181
182impl Default for SheetInit {
183 fn default() -> Self {
184 Self {
185 id: None,
186 name: None,
187 rows: 10,
188 cols: 5,
189 }
190 }
191}
192
193#[derive(Clone, Copy, PartialEq, Eq)]
195enum BlankPolicy {
196 Zero,
198 Skip,
200 Reject,
202}
203
204impl Sheet {
205 pub fn new(args: SheetInit) -> Sheet {
208 let SheetInit {
209 id,
210 name,
211 rows,
212 cols,
213 } = args;
214 let sheet_id = id.unwrap_or_else(generate_unique_id);
215 let sheet_name = name.unwrap_or_else(|| "table_1".to_string());
216
217 let mut columns = Vec::with_capacity(cols);
218 for _ in 0..cols {
219 columns.push(DataColumn::new(rows));
220 }
221
222 let mut uncommitted_actions = Vec::new();
223 for c in 0..cols {
224 for r in 0..rows {
225 uncommitted_actions.push(crate::core::SheetAction::SetCellSrc {
226 sheet_name: sheet_name.clone(),
227 col: c,
228 row: r,
229 src: String::new(),
230 });
231 }
232 }
233
234 Self {
235 id: sheet_id,
236 name: sheet_name,
237 columns,
238 tables: Vec::new(),
239 dependencies: HashMap::new(),
240 dependencies_rev: HashMap::new(),
241 uncommitted_actions,
242 }
243 }
244
245 pub fn setup_after_deserialization(&mut self) {
252 for col in &mut self.columns {
253 col.rebuild_after_load();
254 }
255 self.mark_all_dirty();
256 }
257
258 pub(crate) fn get_all_tables_for_compilation(&self, context: Option<&Context>) -> Vec<Sheet> {
266 let mut list = vec![self.clone()];
267 let mut seen = std::collections::HashSet::new();
268 seen.insert(self.id);
269 if let Some(ctx) = context {
270 for t in ctx.sheets.values() {
271 if !seen.contains(&t.id) {
272 seen.insert(t.id);
273 list.push((*t).clone());
274 }
275 }
276 }
277 list
278 }
279
280 pub fn mark_all_dirty(&mut self) {
285 for col in &mut self.columns {
286 col.dirty_indices.clear();
287 col.dirty_indices.extend(0..col.src.len());
288 }
289 }
290
291 pub fn commit(&mut self, context: Option<&Context>) -> Result<HashSet<CellRef>, EngineError> {
293 let mut queue: VecDeque<CellRef> = VecDeque::new();
294 let mut queue_set: HashSet<CellRef> = HashSet::new();
295 let mut updated_cells: HashSet<CellRef> = HashSet::new();
296
297 for (col_idx, col_data) in self.columns.iter_mut().enumerate() {
299 for row_idx in &col_data.dirty_indices {
300 let cell = CellRef::new(*row_idx, col_idx);
301 queue.push_back(cell);
302 queue_set.insert(cell);
303 updated_cells.insert(cell);
304 }
305 col_data.dirty_indices.clear();
306 }
307
308 let initial_queue_len = queue.len();
309 if initial_queue_len == 0 {
310 return Ok(updated_cells);
311 }
312
313 let start_commit = Instant::now();
314 log::info!(
315 "Sheet '{}' commit starting for {} dirty cells",
316 self.name,
317 initial_queue_len
318 );
319 let max_ops = 10000.max(initial_queue_len * 3);
320 let mut ops = 0;
321
322 let mut tables_for_compilation = self.get_all_tables_for_compilation(context);
323 let mut last_log_time = Instant::now();
324
325 while let Some(cell_ref) = queue.pop_front() {
326 queue_set.remove(&cell_ref);
327 ops += 1;
328 if ops > max_ops {
329 println!("Circular dependency or too many updates detected");
330 break;
331 }
332
333 if ops % 50000 == 0 {
334 log::info!(
335 "Sheet '{}' commit progress: {}/{} cells processed ({:.2?})",
336 self.name,
337 ops,
338 initial_queue_len,
339 last_log_time.elapsed()
340 );
341 last_log_time = Instant::now();
342 }
343
344 let mut detected_num_format: Option<String> = None;
347 let (result, new_deps, compiled_to_cache) = {
348 let src = self.get_src_str_ref(&cell_ref).unwrap_or("");
349 if !src.starts_with('=') {
350 let res = if src.is_empty() {
358 ResultData::None
359 } else if src.starts_with('"') && src.ends_with('"') && src.len() >= 2 {
360 ResultData::String(src[1..src.len() - 1].to_string())
361 } else if let Ok(i) = src.trim().parse::<i64>() {
362 ResultData::Integer(i)
363 } else if let Ok(f) = src.trim().parse::<f64>()
364 && f.is_finite()
372 {
373 ResultData::Float(f)
374 } else if crate::core::engine::result_data::is_excel_error_code(src) {
375 ResultData::Error(src.to_uppercase())
378 } else if src.eq_ignore_ascii_case("true") {
379 ResultData::Boolean(true)
380 } else if src.eq_ignore_ascii_case("false") {
381 ResultData::Boolean(false)
382 } else if let Some((date, format)) =
383 crate::core::date::parse_date(src.trim_matches(' '))
384 {
385 detected_num_format = Some(format.to_format_code());
389 ResultData::Float(crate::core::date::date_to_excel_serial(date))
390 } else if let Some(f) = crate::core::date_fn::parse_time_fraction(src) {
391 ResultData::Float(f)
395 } else {
396 ResultData::String(src.to_string())
397 };
398 (res, vec![], None)
399 } else {
400 let compiled =
401 crate::core::parser::compile_formula(src, &tables_for_compilation);
402 let eval_src =
403 crate::core::parser::serialize_formula(&compiled, &tables_for_compilation);
404 let (res, deps) = match self.eval_with_row(
405 &eval_src,
406 context,
407 Some(cell_ref.row),
408 Some(cell_ref.col),
409 ) {
410 Ok(r) => r,
411 Err(e) => (ResultData::Error(e.to_string()), vec![]),
412 };
413 let final_res = if let ResultData::None = res {
414 ResultData::Float(0.0)
415 } else {
416 res
417 };
418 (final_res, deps, Some(compiled))
419 }
420 };
421
422 if let Some(col) = self.columns.get_mut(cell_ref.col)
424 && cell_ref.row < col.compiled_src.len()
425 {
426 col.compiled_src[cell_ref.row] = compiled_to_cache.unwrap_or_default();
427 }
428
429 if let Some(old_deps) = self.dependencies_rev.remove(&cell_ref) {
432 for provider in old_deps {
433 if let Some(dependents) = self.dependencies.get_mut(&provider) {
434 dependents.remove(&cell_ref);
435 }
436 }
437 }
438
439 if !new_deps.is_empty() {
441 let mut new_deps_set = HashSet::new();
442 for provider in new_deps {
443 new_deps_set.insert(provider.clone());
444 self.dependencies
445 .entry(provider)
446 .or_default()
447 .insert(cell_ref);
448 }
449 self.dependencies_rev.insert(cell_ref, new_deps_set);
450 }
451
452 let inherited = if detected_num_format.is_some()
457 || !matches!(result, ResultData::Float(_) | ResultData::Integer(_))
458 {
459 None
460 } else {
461 self.get_src_str_ref(&cell_ref)
462 .and_then(|src| src.strip_prefix('='))
463 .and_then(|body| crate::core::parser::parse_excel_formula(body).ok())
464 .and_then(|ast| self.inherited_date_format(&ast))
465 };
466 if let Some(code) = detected_num_format.or(inherited) {
469 let existing = self
470 .get_cell_style(cell_ref.row, cell_ref.col)
471 .and_then(|s| s.num_format.clone());
472 if existing.is_none() {
473 self.update_cell_style(cell_ref.row, cell_ref.col, |style| {
474 style.num_format = Some(code);
475 });
476 }
477 }
478
479 if let Some(col) = self.columns.get_mut(cell_ref.col)
481 && cell_ref.row < col.data.len()
482 {
483 col.data.set(cell_ref.row, result.clone());
484 updated_cells.insert(cell_ref);
485 }
486 if let Some(comp_sheet) = tables_for_compilation
487 .iter_mut()
488 .find(|s| s.name == self.name)
489 && let Some(col) = comp_sheet.columns.get_mut(cell_ref.col)
490 && cell_ref.row < col.data.len()
491 {
492 col.data.set(cell_ref.row, result);
493 }
494
495 let local_dep_key = Dependency::Local(cell_ref);
499 if let Some(dependents) = self.dependencies.get(&local_dep_key) {
500 for dependent in dependents {
501 if !queue_set.contains(dependent) {
502 queue.push_back(*dependent);
503 queue_set.insert(*dependent);
504 }
505 }
506 }
507
508 let local_col_dep_key = Dependency::LocalColumn(cell_ref.col);
510 if let Some(dependents) = self.dependencies.get(&local_col_dep_key) {
511 for dependent in dependents {
512 if !queue_set.contains(dependent) {
513 queue.push_back(*dependent);
514 queue_set.insert(*dependent);
515 }
516 }
517 }
518 }
519 if initial_queue_len > 0 {
520 log::info!(
521 "Sheet '{}' commit finished. Processed {} cell updates. Total time: {:.2?}",
522 self.name,
523 ops,
524 start_commit.elapsed()
525 );
526 }
527 Ok(updated_cells)
528 }
529
530 pub fn eval_with_row(
543 &self,
544 input: &str,
545 context: Option<&Context>,
546 row: Option<usize>,
547 col: Option<usize>,
548 ) -> Result<(ResultData, Vec<Dependency>), EngineError> {
549 if input.is_empty() {
550 return Ok((ResultData::None, vec![]));
551 }
552 if let Some(formula) = input.strip_prefix('=') {
553 self.eval_excel(formula, context, row, col)
554 } else {
555 if let Ok(i) = input.parse::<i64>() {
556 Ok((ResultData::Integer(i), vec![]))
557 } else if let Ok(f) = input.parse::<f64>() {
558 Ok((ResultData::Float(f), vec![]))
559 } else if let Ok(b) = input.parse::<bool>() {
560 Ok((ResultData::Boolean(b), vec![]))
561 } else {
562 Ok((ResultData::String(input.to_string()), vec![]))
563 }
564 }
565 }
566
567 pub fn eval(
580 &self,
581 input: &str,
582 context: Option<&Context>,
583 ) -> Result<(ResultData, Vec<Dependency>), EngineError> {
584 self.eval_with_row(input, context, None, None)
585 }
586
587 fn eval_excel(
588 &self,
589 code: &str,
590 context: Option<&Context>,
591 row: Option<usize>,
592 col: Option<usize>,
593 ) -> Result<(ResultData, Vec<Dependency>), EngineError> {
594 let ast = crate::core::parser::parse_excel_formula(code)
595 .map_err(|e| EngineError::EvalError(EvalError::UnknownFunction(e)))?;
596
597 let mut deps = Vec::new();
598 let result = match self.evaluate_ast(&ast, context, row, col, &mut deps, &LetScope::Empty) {
599 Ok(r) => r,
600 Err(EngineError::EvalError(EvalError::UnknownFunction(err_str)))
601 if err_str.starts_with('#') =>
602 {
603 ResultData::Error(err_str)
604 }
605 Err(e) => return Err(e),
606 };
607 Ok((result, deps))
608 }
609
610 fn evaluate_ast(
611 &self,
612 ast: &crate::core::parser::Expr,
613 context: Option<&Context>,
614 row: Option<usize>,
615 col: Option<usize>,
616 deps: &mut Vec<Dependency>,
617 scope: &LetScope<'_>,
618 ) -> Result<ResultData, EngineError> {
619 use crate::core::SheetSection;
620 use crate::core::parser::Expr;
621 use crate::core::parser::Op;
622
623 match ast {
624 Expr::Number(n) => Ok(ResultData::Float(*n)),
625 Expr::String(s) => Ok(ResultData::String(s.clone())),
626 Expr::Boolean(b) => Ok(ResultData::Boolean(*b)),
627 Expr::Error(code) => Ok(ResultData::Error(code.to_string())),
628 Expr::Identifier(name) => match scope.get(name) {
629 Some(val) => Ok(val.clone()),
630 None => Ok(ResultData::Error("#NAME?".to_string())),
631 },
632 Expr::StructuredRef {
633 sheet,
634 column,
635 is_this_row,
636 section,
637 } => {
638 let ref_name = match sheet {
639 Some(name) => name.clone(),
640 None => self.name.clone(),
641 };
642
643 let mut found: Option<(&Sheet, &crate::core::table::ExcelTable)> =
651 self.find_table(&ref_name).map(|t| (self, t));
652 if found.is_none()
653 && let Some(ctx) = context
654 {
655 for s in ctx.sheets.values() {
656 if let Some(t) = s.find_table(&ref_name) {
657 found = Some((s, t));
658 break;
659 }
660 }
661 }
662
663 if let Some((table_sheet, excel_table)) = found {
664 let is_self = table_sheet.name == self.name;
665 let sheet_name = table_sheet.name.clone();
666
667 let col_indices: Vec<(usize, usize)> = if let Some(col_name) = column {
669 let local = excel_table.local_column_index(col_name).ok_or_else(|| {
670 EngineError::EvalError(EvalError::UnknownFunction(format!(
671 "Column not found: {}",
672 col_name
673 )))
674 })?;
675 vec![(local, excel_table.start_col + local)]
676 } else {
677 (0..excel_table.columns.len())
678 .map(|local| (local, excel_table.start_col + local))
679 .collect()
680 };
681 let is_whole_table = column.is_none();
682
683 match section {
684 SheetSection::Headers => {
685 let names: Vec<ResultData> = col_indices
686 .iter()
687 .map(|&(local, _)| {
688 ResultData::String(
689 excel_table.columns.get(local).cloned().unwrap_or_default(),
690 )
691 })
692 .collect();
693 if is_whole_table {
694 Ok(ResultData::List(names))
695 } else {
696 Ok(names.into_iter().next().unwrap_or(ResultData::None))
697 }
698 }
699 SheetSection::Totals => {
700 if let Some(totals_row) = excel_table.totals_row() {
701 let mut results = Vec::new();
702 for &(_, col_idx) in &col_indices {
703 let cell_ref = CellRef::new(totals_row, col_idx);
704 if is_self {
705 deps.push(Dependency::Local(cell_ref));
706 } else {
707 deps.push(Dependency::Remote {
708 sheet: sheet_name.clone(),
709 cell: cell_ref,
710 });
711 }
712 results.push(table_sheet.get_result_data(&cell_ref));
713 }
714 if is_whole_table {
715 Ok(ResultData::List(results))
716 } else {
717 Ok(results.into_iter().next().unwrap_or(ResultData::None))
718 }
719 } else {
720 Ok(ResultData::None)
721 }
722 }
723 SheetSection::Data | SheetSection::All => {
724 if *is_this_row {
725 let r = row.ok_or_else(|| {
726 EngineError::EvalError(EvalError::UnknownFunction(
727 "This row reference cannot be evaluated without row context"
728 .to_string(),
729 ))
730 })?;
731 let mut results = Vec::new();
732 for &(_, col_idx) in &col_indices {
733 let cell_ref = CellRef::new(r, col_idx);
734 if is_self {
735 deps.push(Dependency::Local(cell_ref));
736 } else {
737 deps.push(Dependency::Remote {
738 sheet: sheet_name.clone(),
739 cell: cell_ref,
740 });
741 }
742 results.push(table_sheet.get_result_data(&cell_ref));
743 }
744 if is_whole_table {
745 Ok(ResultData::List(results))
746 } else {
747 Ok(results.into_iter().next().unwrap_or(ResultData::None))
748 }
749 } else {
750 let mut results = Vec::new();
764 for &(_, col_idx) in &col_indices {
765 for r in
766 excel_table.data_start_row()..=excel_table.data_end_row()
767 {
768 let cell_ref = CellRef::new(r, col_idx);
769 if is_self {
770 deps.push(Dependency::Local(cell_ref));
771 } else {
772 deps.push(Dependency::Remote {
773 sheet: sheet_name.clone(),
774 cell: cell_ref,
775 });
776 }
777 results.push(table_sheet.get_result_data(&cell_ref));
778 }
779 }
780 Ok(ResultData::List(results))
781 }
782 }
783 }
784 } else {
785 let sheet_name = ref_name;
789 let is_self = sheet_name == self.name;
790
791 let target_table = if is_self {
792 self
793 } else if let Some(ctx) = context {
794 if let Some(t) = ctx.sheets.get(&sheet_name) {
795 t
796 } else {
797 return Err(EngineError::EvalError(EvalError::UnknownFunction(
798 format!("Sheet not found: {}", sheet_name),
799 )));
800 }
801 } else {
802 return Err(EngineError::EvalError(EvalError::UnknownFunction(format!(
803 "No context to resolve sheet reference: {}",
804 sheet_name
805 ))));
806 };
807
808 let col_indices: Vec<usize> = if let Some(col_name) = column {
812 let pos = target_table
813 .columns
814 .iter()
815 .position(|c| c.name == *col_name)
816 .ok_or_else(|| {
817 EngineError::EvalError(EvalError::UnknownFunction(format!(
818 "Column not found: {}",
819 col_name
820 )))
821 })?;
822 vec![pos]
823 } else {
824 (0..target_table.columns.len()).collect()
825 };
826 let is_whole_table = column.is_none();
827
828 match section {
829 SheetSection::Headers => {
830 let names: Vec<ResultData> = col_indices
831 .iter()
832 .map(|&idx| {
833 ResultData::String(
834 target_table
835 .columns
836 .get(idx)
837 .map(|c| c.name.clone())
838 .unwrap_or_default(),
839 )
840 })
841 .collect();
842 if is_whole_table {
843 Ok(ResultData::List(names))
844 } else {
845 Ok(names.into_iter().next().unwrap_or(ResultData::None))
846 }
847 }
848 SheetSection::Totals => Ok(ResultData::None),
849 SheetSection::Data | SheetSection::All => {
850 if *is_this_row {
851 let r = row.ok_or_else(|| {
852 EngineError::EvalError(EvalError::UnknownFunction(
853 "This row reference cannot be evaluated without row context"
854 .to_string(),
855 ))
856 })?;
857 let mut results = Vec::new();
858 for &col_idx in &col_indices {
859 let cell_ref = CellRef::new(r, col_idx);
860 if is_self {
861 deps.push(Dependency::Local(cell_ref));
862 } else {
863 deps.push(Dependency::Remote {
864 sheet: sheet_name.clone(),
865 cell: cell_ref,
866 });
867 }
868 results.push(target_table.get_result_data(&cell_ref));
869 }
870 if is_whole_table {
871 Ok(ResultData::List(results))
872 } else {
873 Ok(results.into_iter().next().unwrap_or(ResultData::None))
874 }
875 } else {
876 let mut results = Vec::new();
877 for &col_idx in &col_indices {
878 if is_self {
879 deps.push(Dependency::LocalColumn(col_idx));
880 } else {
881 deps.push(Dependency::RemoteColumn {
882 sheet: sheet_name.clone(),
883 col: col_idx,
884 });
885 }
886 for r in 0..target_table.row_count() {
887 let cell_ref = CellRef::new(r, col_idx);
888 results.push(target_table.get_result_data(&cell_ref));
889 }
890 }
891 Ok(ResultData::List(results))
892 }
893 }
894 }
895 }
896 }
897 Expr::CellRef {
898 sheet,
899 row: r_val,
900 col,
901 ..
902 } => {
903 let cell_ref = CellRef::new(*r_val, *col);
904 let is_self = match sheet {
905 Some(name) => name == &self.name,
906 None => true,
907 };
908
909 if is_self {
910 deps.push(Dependency::Local(cell_ref));
911 Ok(self.get_result_data(&cell_ref))
912 } else {
913 let name = sheet.as_ref().unwrap().clone();
914 deps.push(Dependency::Remote {
915 sheet: name.clone(),
916 cell: cell_ref,
917 });
918
919 if let Some(ctx) = context {
920 if let Some(t) = ctx.sheets.get(&name) {
921 Ok(t.get_result_data(&cell_ref))
922 } else {
923 Err(EngineError::EvalError(EvalError::UnknownFunction(format!(
924 "Sheet not found: {}",
925 name
926 ))))
927 }
928 } else {
929 Err(EngineError::EvalError(EvalError::UnknownFunction(
930 "No context to resolve sheet reference".to_string(),
931 )))
932 }
933 }
934 }
935 Expr::RangeRef {
936 sheet,
937 start_row,
938 start_col,
939 end_row,
940 end_col,
941 ..
942 } => {
943 let is_self = match sheet {
944 Some(name) => name == &self.name,
945 None => true,
946 };
947
948 let actual_end_row = if *end_row == usize::MAX {
949 if is_self {
950 self.row_count().saturating_sub(1)
951 } else if let Some(ctx) = context {
952 ctx.sheets
953 .get(sheet.as_ref().unwrap())
954 .map(|t| t.row_count().saturating_sub(1))
955 .unwrap_or(0)
956 } else {
957 0
958 }
959 } else {
960 *end_row
961 };
962
963 let is_col_range = *end_row == usize::MAX;
964
965 let mut seen_col_deps: HashSet<usize> = HashSet::new();
981
982 let mut results = Vec::new();
983 for r in *start_row..=actual_end_row {
984 for c in *start_col..=*end_col {
985 let cell_ref = CellRef::new(r, c);
986 if is_self {
987 if is_col_range {
988 if seen_col_deps.insert(c) {
989 let col_dep = Dependency::LocalColumn(c);
990 if !deps.contains(&col_dep) {
991 deps.push(col_dep);
992 }
993 }
994 } else {
995 deps.push(Dependency::Local(cell_ref));
996 }
997 if row == Some(r) && col == Some(c) {
1014 results.push(ResultData::None);
1015 } else {
1016 results.push(self.get_result_data(&cell_ref));
1017 }
1018 } else {
1019 let name = sheet.as_ref().unwrap().clone();
1020 if is_col_range {
1021 if seen_col_deps.insert(c) {
1022 let col_dep = Dependency::RemoteColumn {
1023 sheet: name.clone(),
1024 col: c,
1025 };
1026 if !deps.contains(&col_dep) {
1027 deps.push(col_dep);
1028 }
1029 }
1030 } else {
1031 deps.push(Dependency::Remote {
1032 sheet: name.clone(),
1033 cell: cell_ref,
1034 });
1035 }
1036 if let Some(ctx) = context {
1037 if let Some(t) = ctx.sheets.get(&name) {
1038 results.push(t.get_result_data(&cell_ref));
1039 } else {
1040 return Err(EngineError::EvalError(
1041 EvalError::UnknownFunction(format!(
1042 "Sheet not found: {}",
1043 name
1044 )),
1045 ));
1046 }
1047 } else {
1048 return Err(EngineError::EvalError(EvalError::UnknownFunction(
1049 "No context to resolve sheet reference".to_string(),
1050 )));
1051 }
1052 }
1053 }
1054 }
1055 Ok(ResultData::List(results))
1056 }
1057 Expr::List(list) => {
1058 let mut results = Vec::new();
1059 for item in list {
1060 results.push(self.evaluate_ast(item, context, row, col, deps, scope)?);
1061 }
1062 Ok(ResultData::List(results))
1063 }
1064 Expr::UnaryOp { op, expr } => {
1065 let val = self.evaluate_ast(expr, context, row, col, deps, scope)?;
1066 match op {
1067 Op::Sub => match val {
1068 ResultData::Float(f) => Ok(ResultData::Float(-f)),
1069 ResultData::Integer(i) => Ok(ResultData::Integer(-i)),
1070 _ => Err(EngineError::EvalError(EvalError::UnknownFunction(
1071 "Unary minus expects number".to_string(),
1072 ))),
1073 },
1074 _ => Ok(val),
1075 }
1076 }
1077 Expr::BinaryOp { op, left, right } => {
1078 let l_val = self.evaluate_ast(left, context, row, col, deps, scope)?;
1079
1080 match op {
1081 Op::Eq | Op::Ne | Op::Lt | Op::Gt | Op::Le | Op::Ge => {
1082 if let ResultData::Error(_) = &l_val {
1083 return Ok(l_val);
1084 }
1085 let r_val = self.evaluate_ast(right, context, row, col, deps, scope)?;
1086 if let ResultData::Error(_) = &r_val {
1087 return Ok(r_val);
1088 }
1089 let ord = Self::compare_excel_values(&l_val, &r_val);
1090 let b = match op {
1091 Op::Eq => ord.is_eq(),
1092 Op::Ne => !ord.is_eq(),
1093 Op::Lt => ord.is_lt(),
1094 Op::Gt => ord.is_gt(),
1095 Op::Le => ord.is_le(),
1096 Op::Ge => ord.is_ge(),
1097 _ => unreachable!(),
1098 };
1099 Ok(ResultData::Boolean(b))
1100 }
1101 _ => {
1102 if let ResultData::Error(_) = &l_val {
1103 return Ok(l_val);
1104 }
1105 let lf = match self.to_f64(&l_val) {
1106 Some(f) => f,
1107 None => return Ok(ResultData::Error("#VALUE!".to_string())),
1108 };
1109 let r_val = self.evaluate_ast(right, context, row, col, deps, scope)?;
1110 if let ResultData::Error(_) = &r_val {
1111 return Ok(r_val);
1112 }
1113 let rf = match self.to_f64(&r_val) {
1114 Some(f) => f,
1115 None => return Ok(ResultData::Error("#VALUE!".to_string())),
1116 };
1117 match op {
1118 Op::Add => Ok(ResultData::Float(lf + rf)),
1119 Op::Sub => Ok(ResultData::Float(lf - rf)),
1120 Op::Mul => Ok(ResultData::Float(lf * rf)),
1121 Op::Div => {
1122 if rf == 0.0 {
1123 return Ok(ResultData::Error("#DIV/0!".to_string()));
1124 }
1125 Ok(ResultData::Float(lf / rf))
1126 }
1127 Op::Exp => {
1128 if lf == 0.0 && rf == 0.0 {
1129 return Ok(ResultData::Error("#NUM!".to_string()));
1130 }
1131 if lf == 0.0 && rf < 0.0 {
1132 return Ok(ResultData::Error("#DIV/0!".to_string()));
1133 }
1134 if lf < 0.0 {
1135 if rf.fract() != 0.0 || rf.abs() > 1e6 {
1136 return Ok(ResultData::Error("#NUM!".to_string()));
1137 }
1138 let res = lf.powi(rf as i32);
1139 if res.is_nan() || res.is_infinite() {
1140 return Ok(ResultData::Error("#NUM!".to_string()));
1141 }
1142 return Ok(ResultData::Float(res));
1143 }
1144 let res = lf.powf(rf);
1145 if res.is_nan() || res.is_infinite() {
1146 return Ok(ResultData::Error("#NUM!".to_string()));
1147 }
1148 Ok(ResultData::Float(res))
1149 }
1150 _ => unreachable!(),
1151 }
1152 }
1153 }
1154 }
1155 Expr::FunctionCall { name, args } => {
1156 self.evaluate_function(name, args, context, row, col, deps, scope)
1157 }
1158 }
1159 }
1160
1161 fn excel_type_rank(val: &ResultData) -> u8 {
1162 match val {
1163 ResultData::None => 0,
1164 ResultData::Integer(_) | ResultData::Float(_) => 1,
1165 ResultData::String(_) => 2,
1166 ResultData::Boolean(_) => 3,
1167 _ => 4,
1168 }
1169 }
1170
1171 fn compare_excel_values(l: &ResultData, r: &ResultData) -> std::cmp::Ordering {
1172 match (l, r) {
1174 (ResultData::None, ResultData::None) => return std::cmp::Ordering::Equal,
1175 (ResultData::None, ResultData::Integer(b)) => {
1176 return 0.0
1177 .partial_cmp(&(*b as f64))
1178 .unwrap_or(std::cmp::Ordering::Equal);
1179 }
1180 (ResultData::None, ResultData::Float(b)) => {
1181 return 0.0.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal);
1182 }
1183 (ResultData::Integer(a), ResultData::None) => {
1184 return (*a as f64)
1185 .partial_cmp(&0.0)
1186 .unwrap_or(std::cmp::Ordering::Equal);
1187 }
1188 (ResultData::Float(a), ResultData::None) => {
1189 return a.partial_cmp(&0.0).unwrap_or(std::cmp::Ordering::Equal);
1190 }
1191 (ResultData::None, ResultData::String(b)) => {
1192 return "".cmp(b.to_lowercase().as_str());
1193 }
1194 (ResultData::String(a), ResultData::None) => {
1195 return a.to_lowercase().as_str().cmp("");
1196 }
1197 (ResultData::None, ResultData::Boolean(b)) => {
1198 return false.cmp(b);
1199 }
1200 (ResultData::Boolean(a), ResultData::None) => {
1201 return a.cmp(&false);
1202 }
1203 _ => {}
1204 }
1205
1206 let rank_l = Self::excel_type_rank(l);
1207 let rank_r = Self::excel_type_rank(r);
1208 if rank_l != rank_r {
1209 return rank_l.cmp(&rank_r);
1210 }
1211 match (l, r) {
1212 (ResultData::Integer(a), ResultData::Integer(b)) => a.cmp(b),
1213 (ResultData::Float(a), ResultData::Float(b)) => {
1214 a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)
1215 }
1216 (ResultData::Integer(a), ResultData::Float(b)) => (*a as f64)
1217 .partial_cmp(b)
1218 .unwrap_or(std::cmp::Ordering::Equal),
1219 (ResultData::Float(a), ResultData::Integer(b)) => a
1220 .partial_cmp(&(*b as f64))
1221 .unwrap_or(std::cmp::Ordering::Equal),
1222 (ResultData::Boolean(a), ResultData::Boolean(b)) => a.cmp(b),
1223 (ResultData::String(a), ResultData::String(b)) => Self::compare_excel_strings(a, b),
1224 _ => std::cmp::Ordering::Equal,
1225 }
1226 }
1227
1228 fn sort_compare_blanks_last(
1238 l: &ResultData,
1239 r: &ResultData,
1240 sort_order: f64,
1241 ) -> std::cmp::Ordering {
1242 match (matches!(l, ResultData::None), matches!(r, ResultData::None)) {
1243 (true, true) => std::cmp::Ordering::Equal,
1244 (true, false) => std::cmp::Ordering::Greater,
1245 (false, true) => std::cmp::Ordering::Less,
1246 (false, false) => {
1247 let ord = Self::compare_excel_values(l, r);
1248 if sort_order < 0.0 { ord.reverse() } else { ord }
1249 }
1250 }
1251 }
1252
1253 fn is_excel_number_str(s: &str) -> bool {
1254 let s = s.trim();
1255 if s.is_empty() {
1256 return false;
1257 }
1258 let bytes = s.as_bytes();
1259 let first = bytes[0];
1260 if first == b'e' || first == b'E' {
1261 return false;
1262 }
1263 if (first == b'+' || first == b'-') && bytes.len() > 1 {
1264 let second = bytes[1];
1265 if second == b'e' || second == b'E' {
1266 return false;
1267 }
1268 }
1269 true
1270 }
1271
1272 fn compare_excel_strings(a: &str, b: &str) -> std::cmp::Ordering {
1293 let a_low = a.to_lowercase();
1294 let b_low = b.to_lowercase();
1295 let strip_hyphens = |s: &str| -> String { s.chars().filter(|&c| c != '-').collect() };
1296 let a_stripped = strip_hyphens(&a_low);
1297 let b_stripped = strip_hyphens(&b_low);
1298 let char_weight = |ch: char| -> u32 {
1299 match ch {
1300 '(' => 2,
1301 ')' => 3,
1302 _ => (ch as u32) + 10,
1303 }
1304 };
1305 for (ca, cb) in a_stripped.chars().zip(b_stripped.chars()) {
1306 if ca != cb {
1307 return char_weight(ca).cmp(&char_weight(cb));
1308 }
1309 }
1310 match a_stripped.len().cmp(&b_stripped.len()) {
1311 std::cmp::Ordering::Equal => a_low.len().cmp(&b_low.len()),
1312 other => other,
1313 }
1314 }
1315
1316 pub(crate) fn clean_float(val: f64) -> f64 {
1323 if val == 0.0 || !val.is_finite() {
1324 return val;
1325 }
1326 let abs_val = val.abs();
1327 let exp = abs_val.log10().floor() as i32;
1328 let factor = 10.0f64.powi(15 - 1 - exp);
1329 if factor.is_finite() && factor != 0.0 {
1330 let rounded = (val * factor).round() / factor;
1331 if (val - rounded).abs() <= 1e-14 * abs_val {
1332 return rounded;
1333 }
1334 }
1335 val
1336 }
1337
1338 pub(crate) fn to_f64(&self, val: &ResultData) -> Option<f64> {
1348 match val {
1349 ResultData::None => Some(0.0),
1350 ResultData::Float(f) => Some(*f),
1351 ResultData::Integer(i) => Some(*i as f64),
1352 ResultData::Boolean(b) => Some(if *b { 1.0 } else { 0.0 }),
1353 ResultData::String(s) => {
1354 let s_trim = s.trim();
1355 if Self::is_excel_number_str(s_trim) {
1356 if let Ok(f) = s_trim.parse::<f64>() {
1357 return Some(f);
1358 }
1359 if let Some((date, _)) = crate::core::date::parse_date(s_trim) {
1360 return Some(crate::core::date::date_to_excel_serial(date));
1361 }
1362 None
1363 } else if let Some((date, _)) = crate::core::date::parse_date(s_trim) {
1364 Some(crate::core::date::date_to_excel_serial(date))
1365 } else {
1366 None
1367 }
1368 }
1369 _ => None,
1370 }
1371 }
1372
1373 fn to_f64_arg(&self, arg_opt: Option<&ResultData>, fn_name: &str) -> Result<f64, EngineError> {
1374 let val = arg_opt.ok_or_else(|| {
1375 EngineError::EvalError(EvalError::UnknownFunction(format!(
1376 "{} requires argument",
1377 fn_name
1378 )))
1379 })?;
1380 if let ResultData::Error(e) = val {
1381 return Err(EngineError::EvalError(EvalError::UnknownFunction(
1382 e.clone(),
1383 )));
1384 }
1385 self.to_f64(val).ok_or_else(|| {
1386 EngineError::EvalError(EvalError::UnknownFunction("#VALUE!".to_string()))
1387 })
1388 }
1389
1390 fn find_error_in_args(args: &[ResultData]) -> Option<ResultData> {
1391 for arg in args {
1392 match arg {
1393 ResultData::Error(_) => return Some(arg.clone()),
1394 ResultData::List(list) => {
1395 if let Some(err) = Self::find_error_in_args(list) {
1396 return Some(err);
1397 }
1398 }
1399 _ => {}
1400 }
1401 }
1402 None
1403 }
1404
1405 fn check_arg_errors(&self, args: &[ResultData], is_direct: &[bool]) -> Option<ResultData> {
1406 for (i, arg) in args.iter().enumerate() {
1407 match arg {
1408 ResultData::Error(_) => return Some(arg.clone()),
1409 ResultData::List(list) => {
1410 if let Some(err) = self.check_arg_errors(list, &[]) {
1411 return Some(err);
1412 }
1413 }
1414 ResultData::String(_)
1415 if is_direct.get(i).copied().unwrap_or(false) && self.to_f64(arg).is_none() =>
1416 {
1417 return Some(ResultData::Error("#VALUE!".to_string()));
1418 }
1419 _ => {}
1420 }
1421 }
1422 None
1423 }
1424
1425 fn sum_helper(&self, arg: &ResultData, is_direct: bool) -> f64 {
1426 match arg {
1427 ResultData::Float(f) => *f,
1428 ResultData::Integer(i) => *i as f64,
1429 ResultData::Boolean(b) => {
1430 if is_direct {
1431 if *b { 1.0 } else { 0.0 }
1432 } else {
1433 0.0
1434 }
1435 }
1436 ResultData::String(_) => {
1437 if is_direct {
1438 self.to_f64(arg).unwrap_or(0.0)
1439 } else {
1440 0.0
1441 }
1442 }
1443 ResultData::List(list) => {
1444 let mut sum = 0.0;
1445 for item in list {
1446 sum += self.sum_helper(item, false);
1447 }
1448 sum
1449 }
1450 _ => 0.0,
1451 }
1452 }
1453
1454 fn flatten_finance_numbers(&self, arg: &ResultData, is_direct: bool) -> Vec<f64> {
1460 match arg {
1461 ResultData::Float(f) => vec![*f],
1462 ResultData::Integer(i) => vec![*i as f64],
1463 ResultData::Boolean(b) => {
1464 if is_direct {
1465 vec![if *b { 1.0 } else { 0.0 }]
1466 } else {
1467 vec![]
1468 }
1469 }
1470 ResultData::String(_) => {
1471 if is_direct {
1472 self.to_f64(arg).into_iter().collect()
1473 } else {
1474 vec![]
1475 }
1476 }
1477 ResultData::List(list) => list
1478 .iter()
1479 .flat_map(|v| self.flatten_finance_numbers(v, false))
1480 .collect(),
1481 _ => vec![],
1482 }
1483 }
1484
1485 fn flatten_stat_numbers(&self, arg: &ResultData, is_direct: bool) -> Vec<f64> {
1486 match arg {
1487 ResultData::Float(f) => vec![*f],
1488 ResultData::Integer(i) => vec![*i as f64],
1489 ResultData::Boolean(b) => {
1490 if is_direct {
1491 vec![if *b { 1.0 } else { 0.0 }]
1492 } else {
1493 vec![]
1494 }
1495 }
1496 ResultData::String(_) => {
1497 if is_direct {
1498 self.to_f64(arg).into_iter().collect()
1499 } else {
1500 vec![]
1501 }
1502 }
1503 ResultData::List(list) => list
1504 .iter()
1505 .flat_map(|v| self.flatten_stat_numbers(v, false))
1506 .collect(),
1507 _ => vec![],
1508 }
1509 }
1510
1511 fn flatten_positional(
1518 &self,
1519 arg: &ResultData,
1520 out: &mut Vec<Option<f64>>,
1521 first_err: &mut Option<String>,
1522 ) {
1523 match arg {
1524 ResultData::List(items) => {
1525 for item in items {
1526 self.flatten_positional(item, out, first_err);
1527 }
1528 }
1529 ResultData::Float(f) => out.push(Some(*f)),
1530 ResultData::Integer(i) => out.push(Some(*i as f64)),
1531 ResultData::Error(e) => {
1532 if first_err.is_none() {
1533 *first_err = Some(e.clone());
1534 }
1535 out.push(None);
1536 }
1537 _ => out.push(None),
1538 }
1539 }
1540
1541 fn positional_numbers(
1542 &self,
1543 arg: Option<&ResultData>,
1544 first_err: &mut Option<String>,
1545 ) -> Vec<Option<f64>> {
1546 let mut out = Vec::new();
1547 if let Some(a) = arg {
1548 self.flatten_positional(a, &mut out, first_err);
1549 }
1550 out
1551 }
1552
1553 fn pair_and_filter(
1576 xs_raw: Vec<Option<f64>>,
1577 ys_raw: Vec<Option<f64>>,
1578 ) -> Result<(Vec<f64>, Vec<f64>), String> {
1579 if xs_raw.len() != ys_raw.len() {
1580 return Err("#N/A".to_string());
1581 }
1582 let mut xs = Vec::with_capacity(xs_raw.len());
1583 let mut ys = Vec::with_capacity(ys_raw.len());
1584 for (x, y) in xs_raw.into_iter().zip(ys_raw) {
1585 if let (Some(x), Some(y)) = (x, y) {
1586 xs.push(x);
1587 ys.push(y);
1588 }
1589 }
1590 Ok((xs, ys))
1591 }
1592
1593 fn paired_args(
1595 &self,
1596 x_arg: Option<&ResultData>,
1597 y_arg: Option<&ResultData>,
1598 ) -> Result<(Vec<f64>, Vec<f64>), String> {
1599 for arg in [x_arg, y_arg].into_iter().flatten() {
1620 let scalar = match arg {
1626 ResultData::List(items) if items.len() == 1 => &items[0],
1627 other => other,
1628 };
1629 if let ResultData::Error(e) = scalar {
1630 return Err(e.clone());
1631 }
1632 if Self::is_empty_scalar_operand(arg) {
1640 return Err("#VALUE!".to_string());
1641 }
1642 }
1643 let mut first_err = None;
1644 let xs_raw = self.positional_numbers(x_arg, &mut first_err);
1645 let ys_raw = self.positional_numbers(y_arg, &mut first_err);
1646 if xs_raw.len() != ys_raw.len() {
1647 return Err("#N/A".to_string());
1648 }
1649 if let Some(e) = first_err {
1650 return Err(e);
1651 }
1652 Self::pair_and_filter(xs_raw, ys_raw)
1653 }
1654
1655 fn flatten_strict_inner(
1686 &self,
1687 arg: &ResultData,
1688 blanks: BlankPolicy,
1689 coerce_text: bool,
1690 out: &mut Vec<f64>,
1691 ) -> Result<(), String> {
1692 match arg {
1693 ResultData::List(items) => {
1694 for item in items {
1695 self.flatten_strict_inner(item, blanks, coerce_text, out)?;
1696 }
1697 Ok(())
1698 }
1699 ResultData::Error(e) => Err(e.clone()),
1700 ResultData::Float(f) => {
1701 out.push(*f);
1702 Ok(())
1703 }
1704 ResultData::Integer(i) => {
1705 out.push(*i as f64);
1706 Ok(())
1707 }
1708 ResultData::None => match blanks {
1709 BlankPolicy::Zero => {
1710 out.push(0.0);
1711 Ok(())
1712 }
1713 BlankPolicy::Skip => Ok(()),
1714 BlankPolicy::Reject => Err("#VALUE!".to_string()),
1715 },
1716 ResultData::String(_) if coerce_text => match self.to_f64(arg) {
1723 Some(f) => {
1724 out.push(f);
1725 Ok(())
1726 }
1727 None => Err("#VALUE!".to_string()),
1728 },
1729 _ => Err("#VALUE!".to_string()),
1730 }
1731 }
1732
1733 fn flatten_strict_numbers(&self, arg: &ResultData) -> Result<Vec<f64>, String> {
1734 let mut out = Vec::new();
1735 self.flatten_strict_inner(arg, BlankPolicy::Zero, true, &mut out)?;
1736 Ok(out)
1737 }
1738
1739 fn flatten_skipping_blanks(&self, arg: Option<&ResultData>) -> Result<Vec<f64>, String> {
1742 let mut out = Vec::new();
1743 if let Some(a) = arg {
1744 self.flatten_strict_inner(a, BlankPolicy::Skip, true, &mut out)?;
1745 }
1746 Ok(out)
1747 }
1748
1749 fn flatten_skipping_blanks_no_text_coercion(
1756 &self,
1757 arg: Option<&ResultData>,
1758 ) -> Result<Vec<f64>, String> {
1759 let mut out = Vec::new();
1760 if let Some(a) = arg {
1761 self.flatten_strict_inner(a, BlankPolicy::Skip, false, &mut out)?;
1762 }
1763 Ok(out)
1764 }
1765
1766 fn flatten_numbers_only(&self, arg: &ResultData) -> Result<Vec<f64>, String> {
1769 let mut out = Vec::new();
1770 self.flatten_strict_inner(arg, BlankPolicy::Reject, false, &mut out)?;
1771 Ok(out)
1772 }
1773
1774 fn aggregate_range_number(val: &ResultData) -> Option<f64> {
1783 match val {
1784 ResultData::Float(f) => Some(*f),
1785 ResultData::Integer(i) => Some(*i as f64),
1786 _ => None,
1787 }
1788 }
1789
1790 fn flatten_numbers_only_arg(&self, arg: Option<&ResultData>) -> Result<Vec<f64>, String> {
1791 match arg {
1792 Some(a) => self.flatten_numbers_only(a),
1793 None => Ok(vec![]),
1794 }
1795 }
1796
1797 fn flatten_args_stat_numbers(
1811 &self,
1812 args: &[ResultData],
1813 is_direct: &[bool],
1814 ) -> Result<Vec<f64>, String> {
1815 let mut out = Vec::new();
1816 for (i, arg) in args.iter().enumerate() {
1817 let direct = is_direct.get(i).copied().unwrap_or(false);
1818 if direct && matches!(arg, ResultData::String(_)) && self.to_f64(arg).is_none() {
1819 return Err("#VALUE!".to_string());
1820 }
1821 out.extend(self.flatten_stat_numbers(arg, direct));
1822 }
1823 Ok(out)
1824 }
1825
1826 fn flatten_stat_numbers_a(
1843 &self,
1844 arg: &ResultData,
1845 is_direct: bool,
1846 ) -> Result<Vec<f64>, String> {
1847 Ok(match arg {
1848 ResultData::Float(f) => vec![*f],
1849 ResultData::Integer(i) => vec![*i as f64],
1850 ResultData::Boolean(b) => vec![if *b { 1.0 } else { 0.0 }],
1851 ResultData::String(_) => {
1852 if is_direct {
1853 match self.to_f64(arg) {
1854 Some(f) => vec![f],
1855 None => return Err("#VALUE!".to_string()),
1856 }
1857 } else {
1858 vec![0.0]
1859 }
1860 }
1861 ResultData::Error(e) => return Err(e.clone()),
1862 ResultData::List(list) => {
1864 let mut out = Vec::new();
1865 for v in list {
1866 out.extend(self.flatten_stat_numbers_a(v, false)?);
1867 }
1868 out
1869 }
1870 ResultData::None => vec![],
1871 _ => vec![0.0],
1872 })
1873 }
1874
1875 fn flatten_args_stat_numbers_a(
1878 &self,
1879 args: &[ResultData],
1880 is_direct: &[bool],
1881 ) -> Result<Vec<f64>, String> {
1882 let mut out = Vec::new();
1883 for (i, arg) in args.iter().enumerate() {
1884 out.extend(
1885 self.flatten_stat_numbers_a(arg, is_direct.get(i).copied().unwrap_or(false))?,
1886 );
1887 }
1888 Ok(out)
1889 }
1890
1891 fn extract_matrix(&self, arg: &ResultData) -> Vec<Vec<f64>> {
1892 match arg {
1893 ResultData::List(list) => {
1894 let mut rows = Vec::new();
1895 for item in list {
1896 match item {
1897 ResultData::List(sub_list) => {
1898 let row: Vec<f64> =
1899 sub_list.iter().flat_map(|v| self.to_f64(v)).collect();
1900 if !row.is_empty() {
1901 rows.push(row);
1902 }
1903 }
1904 _ => {
1905 if let Some(f) = self.to_f64(item) {
1906 rows.push(vec![f]);
1907 }
1908 }
1909 }
1910 }
1911 rows
1912 }
1913 _ => vec![],
1914 }
1915 }
1916
1917 fn matrix_from_arg(
1928 &self,
1929 expr: &crate::core::parser::Expr,
1930 value: &ResultData,
1931 ) -> Vec<Vec<f64>> {
1932 if let ResultData::List(items) = value
1934 && items.iter().any(|i| matches!(i, ResultData::List(_)))
1935 {
1936 return self.extract_matrix(value);
1937 }
1938 fn plain(v: &ResultData) -> Option<f64> {
1943 match v {
1944 ResultData::Float(f) => Some(*f),
1945 ResultData::Integer(i) => Some(*i as f64),
1946 _ => None,
1947 }
1948 }
1949 let items: Vec<&ResultData> = match value {
1950 ResultData::List(items) => items.iter().collect(),
1951 other => vec![other],
1952 };
1953 if items.iter().any(|v| plain(v).is_none()) {
1954 return Vec::new();
1955 }
1956 let flat: Vec<f64> = items.iter().filter_map(|v| plain(v)).collect();
1957 let cols = match Self::range_bounds(expr) {
1958 Some((_, _, start_col, _, end_col)) => end_col.saturating_sub(start_col) + 1,
1959 None => flat.len().max(1),
1960 };
1961 if cols == 0 || !flat.len().is_multiple_of(cols) {
1962 return self.extract_matrix(value);
1963 }
1964 flat.chunks(cols).map(|c| c.to_vec()).collect()
1965 }
1966
1967 fn paired_sum_has_no_numbers(&self, arg: Option<&ResultData>) -> bool {
1985 let mut ignored = None;
1986 let slots = self.positional_numbers(arg, &mut ignored);
1987 slots.iter().all(|v| v.is_none())
1988 }
1989
1990 fn is_empty_scalar_operand(arg: &ResultData) -> bool {
2004 let scalar = match arg {
2005 ResultData::List(items) if items.len() == 1 => &items[0],
2006 other => other,
2007 };
2008 matches!(scalar, ResultData::None)
2009 }
2010
2011 fn first_arg_is_boolean(args: &[ResultData]) -> bool {
2021 matches!(args.first(), Some(ResultData::Boolean(_)))
2022 }
2023
2024 fn opt_f64_arg(&self, args: &[ResultData], i: usize, default: f64) -> Result<f64, EngineError> {
2025 match args.get(i) {
2026 None => Ok(default),
2027 Some(ResultData::None) => Ok(0.0),
2032 Some(ResultData::Error(e)) => Err(EngineError::EvalError(EvalError::UnknownFunction(
2033 e.clone(),
2034 ))),
2035 Some(v) => self.to_f64(v).ok_or_else(|| {
2036 EngineError::EvalError(EvalError::UnknownFunction("#VALUE!".to_string()))
2037 }),
2038 }
2039 }
2040
2041 fn opt_f64(&self, args: &[ResultData], i: usize, default: f64) -> f64 {
2042 args.get(i).and_then(|v| self.to_f64(v)).unwrap_or(default)
2043 }
2044
2045 fn average_helper(&self, arg: &ResultData, is_direct: bool) -> (f64, usize) {
2046 match arg {
2047 ResultData::Float(f) => (*f, 1),
2048 ResultData::Integer(i) => (*i as f64, 1),
2049 ResultData::Boolean(b) => {
2050 if is_direct {
2051 (if *b { 1.0 } else { 0.0 }, 1)
2052 } else {
2053 (0.0, 0)
2054 }
2055 }
2056 ResultData::String(_) => {
2057 if is_direct {
2058 if let Some(f) = self.to_f64(arg) {
2059 (f, 1)
2060 } else {
2061 (0.0, 0)
2062 }
2063 } else {
2064 (0.0, 0)
2065 }
2066 }
2067 ResultData::List(list) => {
2068 let mut sum = 0.0;
2069 let mut count = 0;
2070 for item in list {
2071 let (s, c) = self.average_helper(item, false);
2072 sum += s;
2073 count += c;
2074 }
2075 (sum, count)
2076 }
2077 _ => (0.0, 0),
2078 }
2079 }
2080
2081 fn count_helper(&self, arg: &ResultData) -> usize {
2082 match arg {
2083 ResultData::Float(_) | ResultData::Integer(_) => 1,
2084 ResultData::List(list) => {
2085 let mut count = 0;
2086 for item in list {
2087 count += self.count_helper(item);
2088 }
2089 count
2090 }
2091 _ => 0,
2092 }
2093 }
2094
2095 fn min_helper(&self, arg: &ResultData, is_direct: bool) -> f64 {
2096 match arg {
2097 ResultData::Float(f) => *f,
2098 ResultData::Integer(i) => *i as f64,
2099 ResultData::Boolean(b) => {
2100 if is_direct {
2101 if *b { 1.0 } else { 0.0 }
2102 } else {
2103 f64::INFINITY
2104 }
2105 }
2106 ResultData::String(_) => {
2107 if is_direct {
2108 self.to_f64(arg).unwrap_or(f64::INFINITY)
2109 } else {
2110 f64::INFINITY
2111 }
2112 }
2113 ResultData::List(list) => {
2114 let mut min_val = f64::INFINITY;
2115 for item in list {
2116 min_val = min_val.min(self.min_helper(item, false));
2117 }
2118 min_val
2119 }
2120 _ => f64::INFINITY,
2121 }
2122 }
2123
2124 fn max_helper(&self, arg: &ResultData, is_direct: bool) -> f64 {
2125 match arg {
2126 ResultData::Float(f) => *f,
2127 ResultData::Integer(i) => *i as f64,
2128 ResultData::Boolean(b) => {
2129 if is_direct {
2130 if *b { 1.0 } else { 0.0 }
2131 } else {
2132 f64::NEG_INFINITY
2133 }
2134 }
2135 ResultData::String(_) => {
2136 if is_direct {
2137 self.to_f64(arg).unwrap_or(f64::NEG_INFINITY)
2138 } else {
2139 f64::NEG_INFINITY
2140 }
2141 }
2142 ResultData::List(list) => {
2143 let mut max_val = f64::NEG_INFINITY;
2144 for item in list {
2145 max_val = max_val.max(self.max_helper(item, false));
2146 }
2147 max_val
2148 }
2149 _ => f64::NEG_INFINITY,
2150 }
2151 }
2152
2153 fn concat_helper(&self, arg: &ResultData, out: &mut String) {
2154 match arg {
2155 ResultData::List(list) => {
2156 for item in list {
2157 self.concat_helper(item, out);
2158 }
2159 }
2160 other => {
2161 out.push_str(&other.to_string());
2162 }
2163 }
2164 }
2165
2166 fn counta_helper(&self, arg: &ResultData) -> usize {
2167 match arg {
2168 ResultData::None => 0,
2169 ResultData::List(list) => {
2173 let mut count = 0;
2174 for item in list {
2175 count += self.counta_helper(item);
2176 }
2177 count
2178 }
2179 _ => 1,
2180 }
2181 }
2182
2183 fn product_helper(&self, arg: &ResultData, is_direct: bool) -> (f64, bool) {
2184 match arg {
2185 ResultData::Float(f) => (*f, true),
2186 ResultData::Integer(i) => (*i as f64, true),
2187 ResultData::Boolean(b) => {
2188 if is_direct {
2189 (if *b { 1.0 } else { 0.0 }, true)
2190 } else {
2191 (1.0, false)
2192 }
2193 }
2194 ResultData::String(_) => {
2195 if is_direct {
2196 if let Some(f) = self.to_f64(arg) {
2197 (f, true)
2198 } else {
2199 (1.0, false)
2200 }
2201 } else {
2202 (1.0, false)
2203 }
2204 }
2205 ResultData::List(list) => {
2206 let mut prod = 1.0;
2207 let mut has_nums = false;
2208 for item in list {
2209 let (p, h) = self.product_helper(item, false);
2210 if h {
2211 prod *= p;
2214 has_nums = true;
2215 }
2216 }
2217 (prod, has_nums)
2218 }
2219 _ => (1.0, false),
2220 }
2221 }
2222
2223 fn to_bool_opt(&self, val: &ResultData) -> Option<bool> {
2224 match val {
2225 ResultData::Boolean(b) => Some(*b),
2226 ResultData::Integer(i) => Some(*i != 0),
2227 ResultData::Float(f) => Some(*f != 0.0),
2228 ResultData::String(s) => {
2229 let s_trim = s.trim();
2230 if s_trim.eq_ignore_ascii_case("true") {
2231 Some(true)
2232 } else if s_trim.eq_ignore_ascii_case("false") {
2233 Some(false)
2234 } else if let Ok(f) = s_trim.parse::<f64>() {
2235 Some(f != 0.0)
2236 } else {
2237 None
2238 }
2239 }
2240 ResultData::None => Some(false),
2241 _ => None,
2242 }
2243 }
2244
2245 fn to_bool(&self, val: &ResultData) -> bool {
2246 self.to_bool_opt(val).unwrap_or(false)
2247 }
2248
2249 fn range_numeric(val: &ResultData) -> Option<f64> {
2259 match val {
2260 ResultData::Integer(i) => Some(*i as f64),
2261 ResultData::Float(f) => Some(*f),
2262 _ => None,
2263 }
2264 }
2265
2266 fn exact_lookup_matches(lookup: &ResultData, candidate: &ResultData) -> bool {
2276 if matches!(candidate, ResultData::None) {
2277 return false;
2278 }
2279 let lookup_key = match lookup {
2280 ResultData::None => "0".to_string(),
2281 other => other.to_string(),
2282 };
2283 candidate.to_string() == lookup_key
2284 }
2285
2286 fn wildcard_criteria_matches(pattern: &str, text: &str) -> bool {
2287 fn rec(pat: &[char], txt: &[char]) -> bool {
2288 if pat.is_empty() {
2289 return txt.is_empty();
2290 }
2291 match pat[0] {
2292 '*' => rec(&pat[1..], txt) || (!txt.is_empty() && rec(pat, &txt[1..])),
2293 '?' => !txt.is_empty() && rec(&pat[1..], &txt[1..]),
2294 '~' if pat.len() > 1 && matches!(pat[1], '*' | '?' | '~') => {
2295 !txt.is_empty() && pat[1] == txt[0] && rec(&pat[2..], &txt[1..])
2296 }
2297 ch => !txt.is_empty() && ch == txt[0] && rec(&pat[1..], &txt[1..]),
2298 }
2299 }
2300
2301 let pat = pattern.to_lowercase().chars().collect::<Vec<_>>();
2302 let txt = text.to_lowercase().chars().collect::<Vec<_>>();
2303 rec(&pat, &txt)
2304 }
2305
2306 fn criteria_text_eq(val: &ResultData, pattern: &str) -> bool {
2307 let text = val.to_string();
2308 if pattern.contains('*') || pattern.contains('?') {
2309 matches!(val, ResultData::String(_)) && Self::wildcard_criteria_matches(pattern, &text)
2312 } else {
2313 text.to_lowercase() == pattern.to_lowercase()
2314 }
2315 }
2316
2317 fn match_criteria(&self, val: &ResultData, criteria: &ResultData) -> bool {
2318 let crit_str = criteria.to_string();
2319 if let Some(rest) = crit_str.strip_prefix(">=") {
2320 let val_f = match Self::range_numeric(val) {
2326 Some(f) => f,
2327 None => return false,
2328 };
2329 let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2330 val_f >= crit_f
2331 } else if let Some(rest) = crit_str.strip_prefix('>') {
2332 let val_f = match Self::range_numeric(val) {
2333 Some(f) => f,
2334 None => return false,
2335 };
2336 let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2337 val_f > crit_f
2338 } else if let Some(rest) = crit_str.strip_prefix("<>") {
2339 let remainder = rest.trim();
2340 !Self::criteria_text_eq(val, remainder)
2341 } else if let Some(rest) = crit_str.strip_prefix("<=") {
2342 let val_f = match Self::range_numeric(val) {
2343 Some(f) => f,
2344 None => return false,
2345 };
2346 let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2347 val_f <= crit_f
2348 } else if let Some(rest) = crit_str.strip_prefix('<') {
2349 let val_f = match Self::range_numeric(val) {
2350 Some(f) => f,
2351 None => return false,
2352 };
2353 let crit_f = rest.trim().parse::<f64>().unwrap_or(0.0);
2354 val_f < crit_f
2355 } else if let Some(rest) = crit_str.strip_prefix('=') {
2356 let remainder = rest.trim();
2357 Self::criteria_text_eq(val, remainder)
2358 } else {
2359 Self::criteria_text_eq(val, &crit_str)
2360 }
2361 }
2362
2363 fn range_bounds(
2369 expr: &crate::core::parser::Expr,
2370 ) -> Option<(Option<String>, usize, usize, usize, usize)> {
2371 use crate::core::parser::Expr;
2372 match expr {
2373 Expr::RangeRef {
2374 sheet,
2375 start_row,
2376 start_col,
2377 end_row,
2378 end_col,
2379 ..
2380 } => Some((sheet.clone(), *start_row, *start_col, *end_row, *end_col)),
2381 Expr::CellRef {
2382 sheet, row, col, ..
2383 } => Some((sheet.clone(), *row, *col, *row, *col)),
2384 _ => None,
2385 }
2386 }
2387
2388 fn materialize_range(
2395 &self,
2396 sheet_opt: &Option<String>,
2397 start_row: usize,
2398 start_col: usize,
2399 end_row: usize,
2400 end_col: usize,
2401 context: Option<&Context>,
2402 ) -> Option<Vec<Vec<ResultData>>> {
2403 let is_self = match sheet_opt {
2404 Some(name) => name == &self.name,
2405 None => true,
2406 };
2407 let source: &Sheet = if is_self {
2408 self
2409 } else {
2410 context?.sheets.get(sheet_opt.as_ref()?)?
2411 };
2412 let actual_end_row = if end_row == usize::MAX {
2413 source.row_count().saturating_sub(1)
2414 } else {
2415 end_row
2416 };
2417 if actual_end_row < start_row || end_col < start_col {
2418 return Some(Vec::new());
2419 }
2420 let mut grid = Vec::with_capacity(actual_end_row - start_row + 1);
2421 for r in start_row..=actual_end_row {
2422 let mut row = Vec::with_capacity(end_col - start_col + 1);
2423 for c in start_col..=end_col {
2424 row.push(source.get_result_data(&CellRef::new(r, c)));
2425 }
2426 grid.push(row);
2427 }
2428 Some(grid)
2429 }
2430
2431 fn evaluate_database_function(
2445 &self,
2446 func_name: &str,
2447 args: &[crate::core::parser::Expr],
2448 evaluated_args: &[ResultData],
2449 context: Option<&Context>,
2450 ) -> Result<ResultData, EngineError> {
2451 if args.len() < 3 || evaluated_args.len() < 3 {
2452 return Ok(ResultData::Error("#VALUE!".to_string()));
2453 }
2454 let (db_sheet, db_sr, db_sc, db_er, db_ec) = match Self::range_bounds(&args[0]) {
2455 Some(v) => v,
2456 None => return Ok(ResultData::Error("#VALUE!".to_string())),
2457 };
2458 let (crit_sheet, crit_sr, crit_sc, crit_er, crit_ec) = match Self::range_bounds(&args[2]) {
2459 Some(v) => v,
2460 None => return Ok(ResultData::Error("#VALUE!".to_string())),
2461 };
2462 let db = match self.materialize_range(&db_sheet, db_sr, db_sc, db_er, db_ec, context) {
2463 Some(g) => g,
2464 None => return Ok(ResultData::Error("#REF!".to_string())),
2465 };
2466 let crit = match self.materialize_range(
2467 &crit_sheet,
2468 crit_sr,
2469 crit_sc,
2470 crit_er,
2471 crit_ec,
2472 context,
2473 ) {
2474 Some(g) => g,
2475 None => return Ok(ResultData::Error("#REF!".to_string())),
2476 };
2477 if db.len() < 2 || crit.len() < 2 {
2478 return Ok(ResultData::Error("#VALUE!".to_string()));
2479 }
2480
2481 let db_headers: Vec<String> = db[0].iter().map(|v| v.to_string()).collect();
2482 let field_idx: usize = match &evaluated_args[1] {
2483 ResultData::String(s) => {
2484 match db_headers.iter().position(|h| h.eq_ignore_ascii_case(s)) {
2485 Some(idx) => idx,
2486 None => return Ok(ResultData::Error("#VALUE!".to_string())),
2487 }
2488 }
2489 other => match self.to_f64(other) {
2490 Some(n) if n >= 1.0 && (n as usize) <= db_headers.len() => n as usize - 1,
2491 _ => return Ok(ResultData::Error("#VALUE!".to_string())),
2492 },
2493 };
2494
2495 let crit_headers: Vec<String> = crit[0].iter().map(|v| v.to_string()).collect();
2496 let crit_to_db: Vec<Option<usize>> = crit_headers
2497 .iter()
2498 .map(|h| db_headers.iter().position(|dh| dh.eq_ignore_ascii_case(h)))
2499 .collect();
2500
2501 let mut matched: Vec<ResultData> = Vec::new();
2502 for row in db.iter().skip(1) {
2503 let row_matches_any_criteria_row = crit.iter().skip(1).any(|crit_row| {
2504 crit_row.iter().enumerate().all(|(ci, cell)| {
2505 if matches!(cell, ResultData::None) {
2506 return true;
2507 }
2508 match crit_to_db.get(ci).copied().flatten() {
2509 Some(db_col) => self.match_criteria(&row[db_col], cell),
2510 None => false,
2511 }
2512 })
2513 });
2514 if row_matches_any_criteria_row {
2515 matched.push(row[field_idx].clone());
2516 }
2517 }
2518
2519 match func_name {
2520 "DGET" => match matched.len() {
2521 0 => Ok(ResultData::Error("#VALUE!".to_string())),
2522 1 => Ok(matched.into_iter().next().unwrap()),
2523 _ => Ok(ResultData::Error("#NUM!".to_string())),
2524 },
2525 "DCOUNT" => Ok(ResultData::Float(
2526 matched
2527 .iter()
2528 .filter(|v| Self::range_numeric(v).is_some())
2529 .count() as f64,
2530 )),
2531 "DCOUNTA" => Ok(ResultData::Float(
2532 matched.iter().map(|v| self.counta_helper(v)).sum::<usize>() as f64,
2533 )),
2534 _ => {
2535 let nums: Vec<f64> = matched.iter().filter_map(Self::range_numeric).collect();
2536 match func_name {
2537 "DSUM" => Ok(ResultData::Float(nums.iter().sum())),
2538 "DPRODUCT" => Ok(ResultData::Float(if nums.is_empty() {
2539 0.0
2540 } else {
2541 nums.iter().product()
2542 })),
2543 "DMAX" => {
2544 let m = nums.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
2545 Ok(ResultData::Float(if m.is_finite() { m } else { 0.0 }))
2546 }
2547 "DMIN" => {
2548 let m = nums.iter().cloned().fold(f64::INFINITY, f64::min);
2549 Ok(ResultData::Float(if m.is_finite() { m } else { 0.0 }))
2550 }
2551 "DAVERAGE" => {
2552 if nums.is_empty() {
2553 Ok(ResultData::Error("#DIV/0!".to_string()))
2554 } else {
2555 Ok(ResultData::Float(
2556 nums.iter().sum::<f64>() / nums.len() as f64,
2557 ))
2558 }
2559 }
2560 "DSTDEV" => match crate::core::stats::stdev_s(&nums) {
2561 Ok(v) => Ok(ResultData::Float(v)),
2562 Err(e) => Ok(ResultData::Error(e)),
2563 },
2564 "DSTDEVP" => match crate::core::stats::stdev_p(&nums) {
2565 Ok(v) => Ok(ResultData::Float(v)),
2566 Err(e) => Ok(ResultData::Error(e)),
2567 },
2568 "DVAR" => match crate::core::stats::var_s(&nums) {
2569 Ok(v) => Ok(ResultData::Float(v)),
2570 Err(e) => Ok(ResultData::Error(e)),
2571 },
2572 "DVARP" => match crate::core::stats::var_p(&nums) {
2573 Ok(v) => Ok(ResultData::Float(v)),
2574 Err(e) => Ok(ResultData::Error(e)),
2575 },
2576 _ => unreachable!(),
2577 }
2578 }
2579 }
2580 }
2581
2582 fn proper(&self, s: &str) -> String {
2583 let mut c_chars = Vec::new();
2589 let mut capitalize_next = true;
2590 for c in s.chars() {
2591 if c.is_alphabetic() {
2592 if capitalize_next {
2593 c_chars.extend(c.to_uppercase());
2594 } else {
2595 c_chars.extend(c.to_lowercase());
2596 }
2597 capitalize_next = false;
2598 } else {
2599 c_chars.push(c);
2600 capitalize_next = true;
2601 }
2602 }
2603 c_chars.into_iter().collect()
2604 }
2605
2606 fn get_ymd_hms(&self) -> ((i32, u32, u32), (u32, u32, u32)) {
2607 let now = web_time::SystemTime::now()
2608 .duration_since(web_time::SystemTime::UNIX_EPOCH)
2609 .unwrap_or_default()
2610 .as_secs();
2611 let secs_in_day = 86400;
2612 let days_since_epoch = (now / secs_in_day) as i32;
2613 let seconds_of_day = (now % secs_in_day) as u32;
2614
2615 let hour = seconds_of_day / 3600;
2616 let minute = (seconds_of_day % 3600) / 60;
2617 let second = seconds_of_day % 60;
2618
2619 let era = (if days_since_epoch >= -719468 {
2620 days_since_epoch + 719468
2621 } else {
2622 days_since_epoch + 719468 - 146096
2623 }) / 146097;
2624 let doe = (days_since_epoch + 719468 - era * 146097) as u32;
2625 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
2626 let y = (yoe as i32) + era * 400;
2627 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
2628 let mp = (5 * doy + 2) / 153;
2629 let d = doy - (153 * mp + 2) / 5 + 1;
2630 let m = if mp < 10 { mp + 3 } else { mp - 9 };
2631 let year = if m <= 2 { y + 1 } else { y };
2632
2633 ((year, m, d), (hour, minute, second))
2634 }
2635
2636 fn evaluate_let(
2643 &self,
2644 args: &[crate::core::parser::Expr],
2645 context: Option<&Context>,
2646 row: Option<usize>,
2647 col: Option<usize>,
2648 deps: &mut Vec<Dependency>,
2649 scope: &LetScope<'_>,
2650 ) -> Result<ResultData, EngineError> {
2651 use crate::core::parser::Expr;
2652
2653 if args.is_empty() || args.len().is_multiple_of(2) {
2654 return Ok(ResultData::Error("#VALUE!".to_string()));
2657 }
2658 if args.len() == 1 {
2659 return self.evaluate_ast(&args[0], context, row, col, deps, scope);
2660 }
2661
2662 let name = match &args[0] {
2663 Expr::Identifier(n) => n.as_str(),
2664 _ => return Ok(ResultData::Error("#VALUE!".to_string())),
2665 };
2666 let remaining_pairs = args.len() / 2 - 1;
2669 let is_duplicate = args[2..]
2670 .iter()
2671 .step_by(2)
2672 .take(remaining_pairs)
2673 .any(|a| matches!(a, Expr::Identifier(n2) if n2.eq_ignore_ascii_case(name)));
2674 if is_duplicate {
2675 return Ok(ResultData::Error("#VALUE!".to_string()));
2676 }
2677
2678 let value = self.evaluate_ast(&args[1], context, row, col, deps, scope)?;
2679 let inner_scope = LetScope::Bound {
2680 name,
2681 value: &value,
2682 parent: scope,
2683 };
2684 self.evaluate_let(&args[2..], context, row, col, deps, &inner_scope)
2685 }
2686
2687 fn extract_lambda(
2696 expr: &crate::core::parser::Expr,
2697 ) -> Option<(Vec<&str>, &crate::core::parser::Expr)> {
2698 use crate::core::parser::Expr;
2699 let Expr::FunctionCall { name, args } = expr else {
2700 return None;
2701 };
2702 if !name.eq_ignore_ascii_case("LAMBDA") || args.is_empty() {
2703 return None;
2704 }
2705 let (body, params) = args.split_last().unwrap();
2706 let param_names: Vec<&str> = params
2707 .iter()
2708 .filter_map(|p| match p {
2709 Expr::Identifier(n) => Some(n.as_str()),
2710 _ => None,
2711 })
2712 .collect();
2713 if param_names.len() != params.len() {
2714 return None;
2715 }
2716 Some((param_names, body))
2717 }
2718
2719 #[allow(clippy::too_many_arguments)]
2725 fn invoke_lambda<'v>(
2726 &self,
2727 params: &[&str],
2728 values: &'v [ResultData],
2729 body: &crate::core::parser::Expr,
2730 context: Option<&Context>,
2731 row: Option<usize>,
2732 col: Option<usize>,
2733 deps: &mut Vec<Dependency>,
2734 scope: &LetScope<'v>,
2735 ) -> Result<ResultData, EngineError> {
2736 match (params.split_first(), values.split_first()) {
2737 (Some((&pname, prest)), Some((vfirst, vrest))) => {
2738 let inner_scope = LetScope::Bound {
2739 name: pname,
2740 value: vfirst,
2741 parent: scope,
2742 };
2743 self.invoke_lambda(prest, vrest, body, context, row, col, deps, &inner_scope)
2744 }
2745 _ => self.evaluate_ast(body, context, row, col, deps, scope),
2746 }
2747 }
2748
2749 fn eval_as_array(
2753 &self,
2754 expr: &crate::core::parser::Expr,
2755 context: Option<&Context>,
2756 row: Option<usize>,
2757 col: Option<usize>,
2758 deps: &mut Vec<Dependency>,
2759 scope: &LetScope<'_>,
2760 ) -> Result<Vec<ResultData>, EngineError> {
2761 Ok(
2762 match self.evaluate_ast(expr, context, row, col, deps, scope)? {
2763 ResultData::List(items) => Self::flatten_row_major(items).0,
2764 other => vec![other],
2765 },
2766 )
2767 }
2768
2769 fn flatten_row_major(items: Vec<ResultData>) -> (Vec<ResultData>, Option<usize>) {
2779 if !items.is_empty() && items.iter().all(|v| matches!(v, ResultData::List(_))) {
2780 let cols = match &items[0] {
2781 ResultData::List(inner) => inner.len().max(1),
2782 _ => 1,
2783 };
2784 let flat = items
2785 .into_iter()
2786 .flat_map(|v| match v {
2787 ResultData::List(inner) => inner,
2788 other => vec![other],
2789 })
2790 .collect();
2791 (flat, Some(cols))
2792 } else {
2793 (items, None)
2794 }
2795 }
2796
2797 fn array_shape(
2805 &self,
2806 expr: &crate::core::parser::Expr,
2807 context: Option<&Context>,
2808 row: Option<usize>,
2809 col: Option<usize>,
2810 deps: &mut Vec<Dependency>,
2811 scope: &LetScope<'_>,
2812 ) -> Result<(Vec<ResultData>, usize), EngineError> {
2813 use crate::core::parser::Expr;
2814 let items = match self.evaluate_ast(expr, context, row, col, deps, scope)? {
2815 ResultData::List(items) => items,
2816 other => vec![other],
2817 };
2818 let (flat, nested_cols) = Self::flatten_row_major(items);
2819 if let Some(cols) = nested_cols {
2820 return Ok((flat, cols));
2821 }
2822 let num_cols = match expr {
2823 Expr::RangeRef {
2824 start_col, end_col, ..
2825 } => (end_col - start_col + 1).max(1),
2826 Expr::CellRef { .. } => 1,
2827 Expr::FunctionCall { name, args } => self
2828 .function_call_cols(name, args, context, row, col, deps, scope)
2829 .unwrap_or_else(|| flat.len().max(1)),
2830 _ => flat.len().max(1),
2831 };
2832 Ok((flat, num_cols))
2833 }
2834
2835 #[allow(clippy::too_many_arguments)]
2844 fn function_call_cols(
2845 &self,
2846 name: &str,
2847 args: &[crate::core::parser::Expr],
2848 context: Option<&Context>,
2849 row: Option<usize>,
2850 col: Option<usize>,
2851 deps: &mut Vec<Dependency>,
2852 scope: &LetScope<'_>,
2853 ) -> Option<usize> {
2854 let mut upper = name.to_ascii_uppercase();
2855 if let Some(rest) = upper.strip_prefix("_XLFN.") {
2856 upper = rest.to_string();
2857 }
2858 if let Some(rest) = upper.strip_prefix("_XLWS.") {
2859 upper = rest.to_string();
2860 }
2861 match upper.as_str() {
2862 "TRANSPOSE" => {
2863 let (flat, cols) = self
2864 .array_shape(args.first()?, context, row, col, deps, scope)
2865 .ok()?;
2866 Some((flat.len().checked_div(cols).unwrap_or(0)).max(1))
2867 }
2868 "HSTACK" => {
2869 let mut total = 0usize;
2870 for a in args {
2871 total += self.array_shape(a, context, row, col, deps, scope).ok()?.1;
2872 }
2873 Some(total)
2874 }
2875 "VSTACK" => {
2876 let mut max_cols = 0usize;
2877 for a in args {
2878 max_cols =
2879 max_cols.max(self.array_shape(a, context, row, col, deps, scope).ok()?.1);
2880 }
2881 Some(max_cols)
2882 }
2883 "CHOOSEROWS" => Some(
2884 self.array_shape(args.first()?, context, row, col, deps, scope)
2885 .ok()?
2886 .1,
2887 ),
2888 "CHOOSECOLS" => Some(args.len().saturating_sub(1).max(1)),
2889 "DROP" | "TAKE" => {
2890 let (_, cols) = self
2891 .array_shape(args.first()?, context, row, col, deps, scope)
2892 .ok()?;
2893 let is_take = upper == "TAKE";
2894 match args.get(2) {
2895 Some(e) => {
2896 let n = self
2897 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
2898 .unwrap_or(0.0) as isize;
2899 let (s, e2) = Self::drop_take_bounds(cols as isize, n, is_take);
2900 Some((e2 - s).max(0) as usize)
2901 }
2902 None => Some(if is_take { cols } else { 0 }),
2903 }
2904 }
2905 "EXPAND" => {
2906 let (_, cols) = self
2907 .array_shape(args.first()?, context, row, col, deps, scope)
2908 .ok()?;
2909 match args.get(2) {
2910 Some(e) => Some(
2911 self.to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
2912 .unwrap_or(cols as f64) as usize,
2913 ),
2914 None => Some(cols),
2915 }
2916 }
2917 "TOCOL" => Some(1),
2918 "WRAPROWS" => {
2919 let n = self
2920 .to_f64(
2921 &self
2922 .evaluate_ast(args.get(1)?, context, row, col, deps, scope)
2923 .ok()?,
2924 )
2925 .unwrap_or(1.0)
2926 .max(1.0) as usize;
2927 Some(n)
2928 }
2929 "WRAPCOLS" => {
2930 let (flat, _) = self
2931 .array_shape(args.first()?, context, row, col, deps, scope)
2932 .ok()?;
2933 let wrap = self
2934 .to_f64(
2935 &self
2936 .evaluate_ast(args.get(1)?, context, row, col, deps, scope)
2937 .ok()?,
2938 )
2939 .unwrap_or(1.0)
2940 .max(1.0) as usize;
2941 Some(flat.len().div_ceil(wrap).max(1))
2942 }
2943 "UNIQUE" | "SORT" | "SORTBY" | "FILTER" | "TRIMRANGE" => Some(
2944 self.array_shape(args.first()?, context, row, col, deps, scope)
2945 .ok()?
2946 .1,
2947 ),
2948 "SEQUENCE" => match args.get(1) {
2949 Some(e) => Some(
2950 self.to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
2951 .unwrap_or(1.0)
2952 .max(1.0) as usize,
2953 ),
2954 None => Some(1),
2955 },
2956 "MUNIT" => {
2957 let n = self
2958 .to_f64(
2959 &self
2960 .evaluate_ast(args.first()?, context, row, col, deps, scope)
2961 .ok()?,
2962 )
2963 .unwrap_or(1.0)
2964 .max(1.0) as usize;
2965 Some(n)
2966 }
2967 "MAKEARRAY" => match args.get(1) {
2968 Some(e) => Some(
2969 self.to_f64(&self.evaluate_ast(e, context, row, col, deps, scope).ok()?)
2970 .unwrap_or(1.0)
2971 .max(1.0) as usize,
2972 ),
2973 None => Some(1),
2974 },
2975 _ => None,
2976 }
2977 }
2978
2979 fn drop_take_bounds(total: isize, n: isize, is_take: bool) -> (isize, isize) {
2983 let n = n.clamp(-total, total);
2984 if is_take {
2985 if n >= 0 { (0, n) } else { (total + n, total) }
2986 } else if n >= 0 {
2987 (n, total)
2988 } else {
2989 (0, total + n)
2990 }
2991 }
2992
2993 #[allow(clippy::too_many_arguments)]
3002 fn evaluate_lambda_function(
3003 &self,
3004 func_name: &str,
3005 args: &[crate::core::parser::Expr],
3006 context: Option<&Context>,
3007 row: Option<usize>,
3008 col: Option<usize>,
3009 deps: &mut Vec<Dependency>,
3010 scope: &LetScope<'_>,
3011 ) -> Result<ResultData, EngineError> {
3012 use crate::core::parser::Expr;
3013
3014 match func_name {
3015 "MAP" => {
3016 if args.len() < 2 {
3017 return Ok(ResultData::Error("#VALUE!".to_string()));
3018 }
3019 let (lambda_expr, array_exprs) = args.split_last().unwrap();
3020 let Some((params, body)) = Self::extract_lambda(lambda_expr) else {
3021 return Ok(ResultData::Error("#VALUE!".to_string()));
3022 };
3023 if params.len() != array_exprs.len() {
3024 return Ok(ResultData::Error("#VALUE!".to_string()));
3025 }
3026 let arrays: Vec<Vec<ResultData>> = array_exprs
3027 .iter()
3028 .map(|e| self.eval_as_array(e, context, row, col, deps, scope))
3029 .collect::<Result<_, _>>()?;
3030 let len = arrays.iter().map(|a| a.len()).max().unwrap_or(0);
3031 let mut results = Vec::with_capacity(len);
3032 for i in 0..len {
3033 let values: Vec<ResultData> = arrays
3034 .iter()
3035 .map(|a| a.get(i).cloned().unwrap_or(ResultData::None))
3036 .collect();
3037 results.push(
3038 self.invoke_lambda(¶ms, &values, body, context, row, col, deps, scope)?,
3039 );
3040 }
3041 Ok(ResultData::List(results))
3042 }
3043 "BYROW" | "BYCOL" => {
3044 if args.len() != 2 {
3045 return Ok(ResultData::Error("#VALUE!".to_string()));
3046 }
3047 let Some((params, body)) = Self::extract_lambda(&args[1]) else {
3048 return Ok(ResultData::Error("#VALUE!".to_string()));
3049 };
3050 if params.len() != 1 {
3051 return Ok(ResultData::Error("#VALUE!".to_string()));
3052 }
3053 let num_cols = match &args[0] {
3057 Expr::RangeRef {
3058 start_col, end_col, ..
3059 } => (end_col - start_col + 1).max(1),
3060 _ => 1,
3061 };
3062 let flat = self.eval_as_array(&args[0], context, row, col, deps, scope)?;
3063 let num_rows = if num_cols == 0 {
3064 0
3065 } else {
3066 flat.len().div_ceil(num_cols)
3067 };
3068 let mut results = Vec::new();
3069 if func_name == "BYROW" {
3070 for r in 0..num_rows {
3071 let row_vals: Vec<ResultData> = (0..num_cols)
3072 .filter_map(|c| flat.get(r * num_cols + c).cloned())
3073 .collect();
3074 let arg = vec![ResultData::List(row_vals)];
3075 results.push(
3076 self.invoke_lambda(
3077 ¶ms, &arg, body, context, row, col, deps, scope,
3078 )?,
3079 );
3080 }
3081 } else {
3082 for c in 0..num_cols {
3083 let col_vals: Vec<ResultData> = (0..num_rows)
3084 .filter_map(|r| flat.get(r * num_cols + c).cloned())
3085 .collect();
3086 let arg = vec![ResultData::List(col_vals)];
3087 results.push(
3088 self.invoke_lambda(
3089 ¶ms, &arg, body, context, row, col, deps, scope,
3090 )?,
3091 );
3092 }
3093 }
3094 Ok(ResultData::List(results))
3095 }
3096 "REDUCE" | "SCAN" => {
3097 if args.len() != 2 && args.len() != 3 {
3106 return Ok(ResultData::Error("#VALUE!".to_string()));
3107 }
3108 let lambda_idx = args.len() - 1;
3109 let array_idx = args.len() - 2;
3110 let Some((params, body)) = Self::extract_lambda(&args[lambda_idx]) else {
3111 return Ok(ResultData::Error("#VALUE!".to_string()));
3112 };
3113 if params.len() != 2 {
3114 return Ok(ResultData::Error("#VALUE!".to_string()));
3115 }
3116 let array = self.eval_as_array(&args[array_idx], context, row, col, deps, scope)?;
3117 let (mut acc, rest, mut history): (ResultData, &[ResultData], Vec<ResultData>) =
3123 if args.len() == 3 {
3124 let init = self.evaluate_ast(&args[0], context, row, col, deps, scope)?;
3125 (init, &array[..], Vec::new())
3126 } else {
3127 match array.split_first() {
3128 Some((first, rest)) => (first.clone(), rest, vec![first.clone()]),
3129 None => return Ok(ResultData::Error("#VALUE!".to_string())),
3130 }
3131 };
3132 for item in rest {
3133 let call_args = [acc.clone(), item.clone()];
3134 acc = self
3135 .invoke_lambda(¶ms, &call_args, body, context, row, col, deps, scope)?;
3136 history.push(acc.clone());
3137 }
3138 if func_name == "REDUCE" {
3139 Ok(acc)
3140 } else {
3141 Ok(ResultData::List(history))
3142 }
3143 }
3144 "MAKEARRAY" => {
3145 if args.len() != 3 {
3146 return Ok(ResultData::Error("#VALUE!".to_string()));
3147 }
3148 let Some((params, body)) = Self::extract_lambda(&args[2]) else {
3149 return Ok(ResultData::Error("#VALUE!".to_string()));
3150 };
3151 if params.len() != 2 {
3152 return Ok(ResultData::Error("#VALUE!".to_string()));
3153 }
3154 let rows_val = self.evaluate_ast(&args[0], context, row, col, deps, scope)?;
3155 let cols_val = self.evaluate_ast(&args[1], context, row, col, deps, scope)?;
3156 let num_rows = self.to_f64(&rows_val).unwrap_or(0.0).max(0.0) as usize;
3157 let num_cols = self.to_f64(&cols_val).unwrap_or(0.0).max(0.0) as usize;
3158 let mut results = Vec::with_capacity(num_rows * num_cols);
3159 for r in 1..=num_rows {
3160 for c in 1..=num_cols {
3161 let call_args = [ResultData::Float(r as f64), ResultData::Float(c as f64)];
3162 results.push(self.invoke_lambda(
3163 ¶ms, &call_args, body, context, row, col, deps, scope,
3164 )?);
3165 }
3166 }
3167 Ok(ResultData::List(results))
3168 }
3169 _ => unreachable!(),
3170 }
3171 }
3172
3173 fn parse_a1_reference(text: &str) -> Option<(Option<String>, usize, usize, usize, usize)> {
3182 let text = text.trim();
3183 let (sheet_part, ref_part) = match text.rfind('!') {
3184 Some(idx) => (Some(&text[..idx]), &text[idx + 1..]),
3185 None => (None, text),
3186 };
3187 let sheet = sheet_part.map(|s| s.trim().trim_matches('\'').to_string());
3188
3189 fn parse_cell(s: &str) -> Option<(usize, usize)> {
3190 let s = s.replace('$', "");
3191 let col_end = s.find(|c: char| c.is_ascii_digit())?;
3192 let (col_str, row_str) = s.split_at(col_end);
3193 if col_str.is_empty() || row_str.is_empty() {
3194 return None;
3195 }
3196 let mut col = 0usize;
3197 for ch in col_str.chars() {
3198 if !ch.is_ascii_alphabetic() {
3199 return None;
3200 }
3201 col = col * 26 + (ch.to_ascii_uppercase() as usize - 'A' as usize + 1);
3202 }
3203 let row: usize = row_str.parse().ok()?;
3204 if row == 0 || col == 0 {
3205 return None;
3206 }
3207 Some((row - 1, col - 1))
3208 }
3209
3210 if let Some((start, end)) = ref_part.split_once(':') {
3211 let (r1, c1) = parse_cell(start)?;
3212 let (r2, c2) = parse_cell(end)?;
3213 Some((sheet, r1.min(r2), c1.min(c2), r1.max(r2), c1.max(c2)))
3214 } else {
3215 let (r, c) = parse_cell(ref_part)?;
3216 Some((sheet, r, c, r, c))
3217 }
3218 }
3219
3220 fn read_cell_with_deps(
3227 &self,
3228 sheet_opt: &Option<String>,
3229 r: usize,
3230 c: usize,
3231 context: Option<&Context>,
3232 deps: &mut Vec<Dependency>,
3233 ) -> ResultData {
3234 let is_self = sheet_opt.as_deref().is_none_or(|n| n == self.name);
3235 if is_self {
3236 deps.push(Dependency::Local(CellRef::new(r, c)));
3237 self.get_result_data(&CellRef::new(r, c))
3238 } else if let Some(ctx) = context {
3239 let name = sheet_opt.clone().unwrap();
3240 deps.push(Dependency::Remote {
3241 sheet: name.clone(),
3242 cell: CellRef::new(r, c),
3243 });
3244 ctx.sheets
3245 .get(&name)
3246 .map(|s| s.get_result_data(&CellRef::new(r, c)))
3247 .unwrap_or(ResultData::None)
3248 } else {
3249 ResultData::None
3250 }
3251 }
3252
3253 #[allow(clippy::too_many_arguments)]
3262 fn evaluate_range_info_function(
3263 &self,
3264 func_name: &str,
3265 args: &[crate::core::parser::Expr],
3266 context: Option<&Context>,
3267 row: Option<usize>,
3268 col: Option<usize>,
3269 deps: &mut Vec<Dependency>,
3270 scope: &LetScope<'_>,
3271 ) -> Result<ResultData, EngineError> {
3272 use crate::core::parser::Expr;
3273
3274 match func_name {
3275 "ROW" => match args.first() {
3276 Some(arg) => match Self::range_bounds(arg) {
3281 Some((_, start_row, _, end_row, _)) if end_row > start_row => {
3282 Ok(ResultData::List(
3283 (start_row..=end_row)
3284 .map(|r| ResultData::Float((r + 1) as f64))
3285 .collect(),
3286 ))
3287 }
3288 Some((_, start_row, _, _, _)) => Ok(ResultData::Float((start_row + 1) as f64)),
3289 None => Ok(ResultData::Error("#VALUE!".to_string())),
3290 },
3291 None => match row {
3292 Some(r) => Ok(ResultData::Float((r + 1) as f64)),
3293 None => Ok(ResultData::Error("#VALUE!".to_string())),
3294 },
3295 },
3296 "COLUMN" => match args.first() {
3297 Some(arg) => match Self::range_bounds(arg) {
3300 Some((_, _, start_col, _, end_col)) if end_col > start_col => {
3301 Ok(ResultData::List(
3302 (start_col..=end_col)
3303 .map(|c| ResultData::Float((c + 1) as f64))
3304 .collect(),
3305 ))
3306 }
3307 Some((_, _, start_col, _, _)) => Ok(ResultData::Float((start_col + 1) as f64)),
3308 None => Ok(ResultData::Error("#VALUE!".to_string())),
3309 },
3310 None => match col {
3311 Some(c) => Ok(ResultData::Float((c + 1) as f64)),
3312 None => Ok(ResultData::Error("#VALUE!".to_string())),
3313 },
3314 },
3315 "ROWS" => {
3316 let Some(arg) = args.first() else {
3317 return Ok(ResultData::Error("#VALUE!".to_string()));
3318 };
3319 let Some((sheet_opt, start_row, _, end_row, _)) = Self::range_bounds(arg) else {
3320 return Ok(ResultData::Error("#VALUE!".to_string()));
3321 };
3322 let is_self = sheet_opt.as_deref().is_none_or(|n| n == self.name);
3323 let actual_end_row = if end_row == usize::MAX {
3324 if is_self {
3325 self.row_count().saturating_sub(1)
3326 } else {
3327 context
3328 .and_then(|ctx| sheet_opt.as_ref().and_then(|n| ctx.sheets.get(n)))
3329 .map(|s| s.row_count().saturating_sub(1))
3330 .unwrap_or(0)
3331 }
3332 } else {
3333 end_row
3334 };
3335 Ok(ResultData::Float(
3336 (actual_end_row.saturating_sub(start_row) + 1) as f64,
3337 ))
3338 }
3339 "COLUMNS" => {
3340 let Some(arg) = args.first() else {
3341 return Ok(ResultData::Error("#VALUE!".to_string()));
3342 };
3343 match Self::range_bounds(arg) {
3344 Some((_, _, start_col, _, end_col)) => Ok(ResultData::Float(
3345 (end_col.saturating_sub(start_col) + 1) as f64,
3346 )),
3347 None => Ok(ResultData::Error("#VALUE!".to_string())),
3348 }
3349 }
3350 "AREAS" => {
3351 if args.is_empty() {
3355 Ok(ResultData::Error("#VALUE!".to_string()))
3356 } else {
3357 Ok(ResultData::Float(1.0))
3358 }
3359 }
3360 "ISREF" => Ok(ResultData::Boolean(matches!(
3361 args.first(),
3362 Some(Expr::CellRef { .. } | Expr::RangeRef { .. } | Expr::StructuredRef { .. })
3363 ))),
3364 "FORMULATEXT" | "ISFORMULA" => {
3365 let Some(arg) = args.first() else {
3366 return Ok(ResultData::Error("#VALUE!".to_string()));
3367 };
3368 let Some((sheet_opt, r, c, _, _)) = Self::range_bounds(arg) else {
3369 return Ok(ResultData::Error("#VALUE!".to_string()));
3370 };
3371 let is_self = sheet_opt.as_deref().is_none_or(|n| n == self.name);
3372 let src = if is_self {
3373 deps.push(Dependency::Local(CellRef::new(r, c)));
3374 self.get_src_str(&CellRef::new(r, c))
3375 } else if let Some(ctx) = context {
3376 let name = sheet_opt.unwrap();
3377 deps.push(Dependency::Remote {
3378 sheet: name.clone(),
3379 cell: CellRef::new(r, c),
3380 });
3381 ctx.sheets
3382 .get(&name)
3383 .map(|s| s.get_src_str(&CellRef::new(r, c)))
3384 .unwrap_or_default()
3385 } else {
3386 String::new()
3387 };
3388 let is_formula = src.starts_with('=');
3389 if func_name == "ISFORMULA" {
3390 Ok(ResultData::Boolean(is_formula))
3391 } else if is_formula {
3392 Ok(ResultData::String(src))
3393 } else {
3394 Ok(ResultData::Error("#N/A".to_string()))
3395 }
3396 }
3397 "SHEETS" => Ok(ResultData::Float(
3398 context.map(|c| c.sheets.len() + 1).unwrap_or(1) as f64,
3399 )),
3400 "SHEET" => {
3401 let sheet_name = match args.first() {
3407 None => Some(self.name.clone()),
3408 Some(arg) => match Self::range_bounds(arg) {
3409 Some((sheet_opt, ..)) => {
3410 Some(sheet_opt.unwrap_or_else(|| self.name.clone()))
3411 }
3412 None => self
3413 .evaluate_ast(arg, context, row, col, deps, scope)
3414 .ok()
3415 .map(|v| v.to_string()),
3416 },
3417 };
3418
3419 match sheet_name {
3420 Some(name) => {
3421 let ordinal = context
3422 .and_then(|c| {
3423 c.sheet_order
3424 .iter()
3425 .position(|n| n.eq_ignore_ascii_case(&name))
3426 })
3427 .map(|i| i + 1)
3428 .unwrap_or(1);
3434 Ok(ResultData::Float(ordinal as f64))
3435 }
3436 None => Ok(ResultData::Error("#N/A".to_string())),
3437 }
3438 }
3439 "CELL" => {
3440 if args.is_empty() {
3441 return Ok(ResultData::Error("#VALUE!".to_string()));
3442 }
3443 let info_type = self
3444 .evaluate_ast(&args[0], context, row, col, deps, scope)?
3445 .to_string()
3446 .to_lowercase();
3447 let bounds = args.get(1).and_then(Self::range_bounds);
3448 match info_type.as_str() {
3449 "row" => match bounds.map(|b| b.1).or(row) {
3450 Some(r) => Ok(ResultData::Float((r + 1) as f64)),
3451 None => Ok(ResultData::Error("#VALUE!".to_string())),
3452 },
3453 "col" => match bounds {
3454 Some((_, _, c, _, _)) => Ok(ResultData::Float((c + 1) as f64)),
3455 None => Ok(ResultData::Error("#VALUE!".to_string())),
3456 },
3457 "address" => match bounds {
3458 Some((_, r, c, _, _)) => Ok(ResultData::String(format!(
3459 "${}${}",
3460 crate::core::parser::col_idx_to_letters(c),
3461 r + 1
3462 ))),
3463 None => Ok(ResultData::Error("#VALUE!".to_string())),
3464 },
3465 "contents" => match bounds {
3466 Some((sheet_opt, r, c, _, _)) => {
3467 Ok(self.read_cell_with_deps(&sheet_opt, r, c, context, deps))
3468 }
3469 None => Ok(ResultData::Error("#VALUE!".to_string())),
3470 },
3471 _ => Ok(ResultData::Error("#VALUE!".to_string())),
3472 }
3473 }
3474 "INFO" => {
3475 if args.is_empty() {
3476 return Ok(ResultData::Error("#VALUE!".to_string()));
3477 }
3478 let info_type = self
3479 .evaluate_ast(&args[0], context, row, col, deps, scope)?
3480 .to_string()
3481 .to_lowercase();
3482 match info_type.as_str() {
3483 "numfile" => Ok(ResultData::Float(
3484 context.map(|c| c.sheets.len() + 1).unwrap_or(1) as f64,
3485 )),
3486 "release" => Ok(ResultData::String("16.0".to_string())),
3487 "system" => Ok(ResultData::String(
3488 if cfg!(target_os = "macos") {
3489 "mac"
3490 } else {
3491 "pcdos"
3492 }
3493 .to_string(),
3494 )),
3495 _ => Ok(ResultData::Error("#VALUE!".to_string())),
3496 }
3497 }
3498 "INDIRECT" => {
3499 if args.is_empty() {
3500 return Ok(ResultData::Error("#VALUE!".to_string()));
3501 }
3502 let text = self
3503 .evaluate_ast(&args[0], context, row, col, deps, scope)?
3504 .to_string();
3505 let a1_style = match args.get(1) {
3506 Some(a) => self.to_bool(&self.evaluate_ast(a, context, row, col, deps, scope)?),
3507 None => true,
3508 };
3509 if !a1_style {
3510 return Ok(ResultData::Error("#VALUE!".to_string()));
3512 }
3513 match Self::parse_a1_reference(&text) {
3514 Some((sheet_opt, start_row, start_col, end_row, end_col)) => {
3515 if start_row == end_row && start_col == end_col {
3516 Ok(self.read_cell_with_deps(
3517 &sheet_opt, start_row, start_col, context, deps,
3518 ))
3519 } else {
3520 match self.materialize_range(
3521 &sheet_opt, start_row, start_col, end_row, end_col, context,
3522 ) {
3523 Some(grid) => {
3524 Ok(ResultData::List(grid.into_iter().flatten().collect()))
3525 }
3526 None => Ok(ResultData::Error("#REF!".to_string())),
3527 }
3528 }
3529 }
3530 None => Ok(ResultData::Error("#REF!".to_string())),
3531 }
3532 }
3533 "OFFSET" => {
3534 if args.len() < 3 {
3535 return Ok(ResultData::Error("#VALUE!".to_string()));
3536 }
3537 let Some((sheet_opt, base_row, base_col, base_end_row, base_end_col)) =
3538 Self::range_bounds(&args[0])
3539 else {
3540 return Ok(ResultData::Error("#VALUE!".to_string()));
3541 };
3542 let row_offset = self
3543 .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3544 .unwrap_or(0.0) as isize;
3545 let col_offset = self
3546 .to_f64(&self.evaluate_ast(&args[2], context, row, col, deps, scope)?)
3547 .unwrap_or(0.0) as isize;
3548 let base_height = (base_end_row.saturating_sub(base_row) + 1) as isize;
3549 let base_width = (base_end_col.saturating_sub(base_col) + 1) as isize;
3550 let height = match args.get(3) {
3551 Some(a) => self
3552 .to_f64(&self.evaluate_ast(a, context, row, col, deps, scope)?)
3553 .unwrap_or(base_height as f64) as isize,
3554 None => base_height,
3555 };
3556 let width = match args.get(4) {
3557 Some(a) => self
3558 .to_f64(&self.evaluate_ast(a, context, row, col, deps, scope)?)
3559 .unwrap_or(base_width as f64) as isize,
3560 None => base_width,
3561 };
3562 let new_row = base_row as isize + row_offset;
3563 let new_col = base_col as isize + col_offset;
3564 if new_row < 0 || new_col < 0 || height <= 0 || width <= 0 {
3565 return Ok(ResultData::Error("#REF!".to_string()));
3566 }
3567 let (start_row, start_col) = (new_row as usize, new_col as usize);
3568 let (end_row, end_col) = (
3569 start_row + (height - 1) as usize,
3570 start_col + (width - 1) as usize,
3571 );
3572 if start_row == end_row && start_col == end_col {
3573 Ok(self.read_cell_with_deps(&sheet_opt, start_row, start_col, context, deps))
3574 } else {
3575 match self.materialize_range(
3576 &sheet_opt, start_row, start_col, end_row, end_col, context,
3577 ) {
3578 Some(grid) => Ok(ResultData::List(grid.into_iter().flatten().collect())),
3579 None => Ok(ResultData::Error("#REF!".to_string())),
3580 }
3581 }
3582 }
3583 _ => unreachable!(),
3584 }
3585 }
3586
3587 fn evaluate_getpivotdata(
3594 &self,
3595 args: &[crate::core::parser::Expr],
3596 context: Option<&Context>,
3597 row: Option<usize>,
3598 col: Option<usize>,
3599 deps: &mut Vec<Dependency>,
3600 scope: &LetScope<'_>,
3601 ) -> Result<ResultData, EngineError> {
3602 if args.len() < 2 || !(args.len() - 2).is_multiple_of(2) {
3603 return Ok(ResultData::Error("#VALUE!".to_string()));
3604 }
3605
3606 let data_field = self
3607 .evaluate_ast(&args[0], context, row, col, deps, scope)?
3608 .to_string();
3609
3610 let (sheet_opt, target_row, target_col, _, _) = match Self::range_bounds(&args[1]) {
3611 Some(bounds) => bounds,
3612 None => return Ok(ResultData::Error("#REF!".to_string())),
3613 };
3614 self.read_cell_with_deps(&sheet_opt, target_row, target_col, context, deps);
3617
3618 let sheet_id = match &sheet_opt {
3619 None => self.id,
3620 Some(name) if name == &self.name => self.id,
3621 Some(name) => match context.and_then(|c| c.sheets.get(name)) {
3622 Some(s) => s.id,
3623 None => return Ok(ResultData::Error("#REF!".to_string())),
3624 },
3625 };
3626
3627 let pivot_tables = context.map(|c| c.pivot_tables).unwrap_or(&[]);
3628 let pivot = match pivot_tables.iter().find(|p| {
3629 p.dest_sheet_id == sheet_id
3630 && p.last_output_end_row
3631 .is_some_and(|end| target_row >= p.dest_row && target_row <= end)
3632 && p.last_output_end_col
3633 .is_some_and(|end| target_col >= p.dest_col && target_col <= end)
3634 }) {
3635 Some(p) => p,
3636 None => return Ok(ResultData::Error("#REF!".to_string())),
3637 };
3638
3639 let mut criteria: Vec<(String, String)> = Vec::new();
3640 let mut i = 2;
3641 while i < args.len() {
3642 let field = self
3643 .evaluate_ast(&args[i], context, row, col, deps, scope)?
3644 .to_string();
3645 let item = self
3646 .evaluate_ast(&args[i + 1], context, row, col, deps, scope)?
3647 .to_string();
3648 criteria.push((field, item));
3649 i += 2;
3650 }
3651
3652 let mut sheet_refs: Vec<&Sheet> = context
3653 .map(|c| c.sheets.values().copied().collect())
3654 .unwrap_or_default();
3655 sheet_refs.push(self);
3656
3657 match crate::core::pivot::getpivotdata(&sheet_refs, pivot, &data_field, &criteria) {
3658 Ok(v) => Ok(v),
3659 Err(e) => Ok(ResultData::Error(e)),
3660 }
3661 }
3662
3663 #[allow(clippy::too_many_arguments)]
3678 fn evaluate_array_reshape_function(
3679 &self,
3680 func_name: &str,
3681 args: &[crate::core::parser::Expr],
3682 context: Option<&Context>,
3683 row: Option<usize>,
3684 col: Option<usize>,
3685 deps: &mut Vec<Dependency>,
3686 scope: &LetScope<'_>,
3687 ) -> Result<ResultData, EngineError> {
3688 match func_name {
3689 "TRANSPOSE" => {
3690 let Some(arg) = args.first() else {
3691 return Ok(ResultData::Error("#VALUE!".to_string()));
3692 };
3693 let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3694 let rows = flat.len().checked_div(cols).unwrap_or(0);
3695 let mut result = Vec::with_capacity(flat.len());
3696 for c in 0..cols {
3697 for r in 0..rows {
3698 result.push(flat[r * cols + c].clone());
3699 }
3700 }
3701 Ok(ResultData::List(result))
3702 }
3703 "HSTACK" | "VSTACK" => {
3704 if args.is_empty() {
3705 return Ok(ResultData::Error("#VALUE!".to_string()));
3706 }
3707 let mut shapes = Vec::with_capacity(args.len());
3708 for a in args {
3709 shapes.push(self.array_shape(a, context, row, col, deps, scope)?);
3710 }
3711 let mut result = Vec::new();
3712 if func_name == "HSTACK" {
3713 let max_rows = shapes
3714 .iter()
3715 .map(|(f, c)| if *c == 0 { 0 } else { f.len() / c })
3716 .max()
3717 .unwrap_or(0);
3718 for r in 0..max_rows {
3719 for (flat, cols) in &shapes {
3720 let rows = if *cols == 0 { 0 } else { flat.len() / cols };
3721 for c in 0..*cols {
3722 result.push(if r < rows {
3723 flat[r * cols + c].clone()
3724 } else {
3725 ResultData::Error("#N/A".to_string())
3726 });
3727 }
3728 }
3729 }
3730 } else {
3731 let max_cols = shapes.iter().map(|(_, c)| *c).max().unwrap_or(0);
3732 for (flat, cols) in &shapes {
3733 let rows = if *cols == 0 { 0 } else { flat.len() / cols };
3734 for r in 0..rows {
3735 for c in 0..max_cols {
3736 result.push(if c < *cols {
3737 flat[r * cols + c].clone()
3738 } else {
3739 ResultData::Error("#N/A".to_string())
3740 });
3741 }
3742 }
3743 }
3744 }
3745 Ok(ResultData::List(result))
3746 }
3747 "CHOOSEROWS" | "CHOOSECOLS" => {
3748 if args.len() < 2 {
3749 return Ok(ResultData::Error("#VALUE!".to_string()));
3750 }
3751 let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3752 let rows = flat.len().checked_div(cols).unwrap_or(0);
3753 let total = if func_name == "CHOOSEROWS" {
3754 rows
3755 } else {
3756 cols
3757 } as isize;
3758 let mut indices = Vec::with_capacity(args.len() - 1);
3759 for idx_expr in &args[1..] {
3760 let n = self
3761 .to_f64(&self.evaluate_ast(idx_expr, context, row, col, deps, scope)?)
3762 .unwrap_or(0.0) as isize;
3763 let real_idx = if n < 0 { total + n } else { n - 1 };
3764 if real_idx < 0 || real_idx >= total {
3765 return Ok(ResultData::Error("#VALUE!".to_string()));
3766 }
3767 indices.push(real_idx as usize);
3768 }
3769 let mut result = Vec::new();
3770 if func_name == "CHOOSEROWS" {
3771 for r in indices {
3772 for c in 0..cols {
3773 result.push(flat[r * cols + c].clone());
3774 }
3775 }
3776 } else {
3777 for r in 0..rows {
3778 for &c in &indices {
3779 result.push(flat[r * cols + c].clone());
3780 }
3781 }
3782 }
3783 Ok(ResultData::List(result))
3784 }
3785 "DROP" | "TAKE" => {
3786 if args.len() < 2 {
3787 return Ok(ResultData::Error("#VALUE!".to_string()));
3788 }
3789 let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3790 let num_rows = flat.len().checked_div(cols).unwrap_or(0) as isize;
3791 let is_take = func_name == "TAKE";
3792 let rows_n = self
3793 .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3794 .unwrap_or(0.0) as isize;
3795 let cols_n = match args.get(2) {
3796 Some(e) => self
3797 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3798 .unwrap_or(0.0) as isize,
3799 None => {
3800 if is_take {
3801 cols as isize
3802 } else {
3803 0
3804 }
3805 }
3806 };
3807 let (row_start, row_end) = Self::drop_take_bounds(num_rows, rows_n, is_take);
3808 let (col_start, col_end) = Self::drop_take_bounds(cols as isize, cols_n, is_take);
3809 if row_start >= row_end || col_start >= col_end {
3810 return Ok(ResultData::Error("#CALC!".to_string()));
3811 }
3812 let mut result = Vec::new();
3813 for r in row_start..row_end {
3814 for c in col_start..col_end {
3815 result.push(flat[(r as usize) * cols + (c as usize)].clone());
3816 }
3817 }
3818 Ok(ResultData::List(result))
3819 }
3820 "EXPAND" => {
3821 if args.len() < 2 {
3822 return Ok(ResultData::Error("#VALUE!".to_string()));
3823 }
3824 let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3825 let orig_rows = flat.len().checked_div(cols).unwrap_or(0);
3826 let new_rows = self
3827 .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3828 .unwrap_or(orig_rows as f64) as usize;
3829 let new_cols = match args.get(2) {
3830 Some(e) => self
3831 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3832 .unwrap_or(cols as f64) as usize,
3833 None => cols,
3834 };
3835 let pad = match args.get(3) {
3836 Some(e) => self.evaluate_ast(e, context, row, col, deps, scope)?,
3837 None => ResultData::Error("#N/A".to_string()),
3838 };
3839 if new_rows < orig_rows || new_cols < cols {
3840 return Ok(ResultData::Error("#VALUE!".to_string()));
3841 }
3842 let mut result = Vec::with_capacity(new_rows * new_cols);
3843 for r in 0..new_rows {
3844 for c in 0..new_cols {
3845 result.push(if r < orig_rows && c < cols {
3846 flat[r * cols + c].clone()
3847 } else {
3848 pad.clone()
3849 });
3850 }
3851 }
3852 Ok(ResultData::List(result))
3853 }
3854 "TOCOL" | "TOROW" => {
3855 let Some(arg) = args.first() else {
3856 return Ok(ResultData::Error("#VALUE!".to_string()));
3857 };
3858 let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3859 let rows = flat.len().checked_div(cols).unwrap_or(0);
3860 let ignore = match args.get(1) {
3861 Some(e) => self
3862 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3863 .unwrap_or(0.0) as i64,
3864 None => 0,
3865 };
3866 let scan_by_col = match args.get(2) {
3867 Some(e) => self.to_bool(&self.evaluate_ast(e, context, row, col, deps, scope)?),
3868 None => false,
3869 };
3870 let ordered: Vec<ResultData> = if scan_by_col {
3871 let mut v = Vec::with_capacity(flat.len());
3872 for c in 0..cols {
3873 for r in 0..rows {
3874 v.push(flat[r * cols + c].clone());
3875 }
3876 }
3877 v
3878 } else {
3879 flat
3880 };
3881 let filtered: Vec<ResultData> = ordered
3882 .into_iter()
3883 .filter(|v| match ignore {
3884 1 => !matches!(v, ResultData::None),
3885 2 => !matches!(v, ResultData::Error(_)),
3886 3 => !matches!(v, ResultData::None | ResultData::Error(_)),
3887 _ => true,
3888 })
3889 .collect();
3890 Ok(ResultData::List(filtered))
3891 }
3892 "WRAPROWS" | "WRAPCOLS" => {
3893 if args.len() < 2 {
3894 return Ok(ResultData::Error("#VALUE!".to_string()));
3895 }
3896 let (flat, _cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
3897 let wrap = self
3898 .to_f64(&self.evaluate_ast(&args[1], context, row, col, deps, scope)?)
3899 .unwrap_or(1.0)
3900 .max(1.0) as usize;
3901 let pad = match args.get(2) {
3902 Some(e) => self.evaluate_ast(e, context, row, col, deps, scope)?,
3903 None => ResultData::Error("#N/A".to_string()),
3904 };
3905 if func_name == "WRAPROWS" {
3906 let mut result = flat;
3909 let rem = result.len() % wrap;
3910 if rem != 0 {
3911 result.extend(std::iter::repeat_n(pad, wrap - rem));
3912 }
3913 Ok(ResultData::List(result))
3914 } else {
3915 let num_result_cols = flat.len().div_ceil(wrap).max(1);
3916 let total = wrap * num_result_cols;
3917 let mut result = Vec::with_capacity(total);
3918 for i in 0..total {
3919 let col = i / wrap;
3920 let r = i % wrap;
3921 let target = r * num_result_cols + col;
3922 while result.len() <= target {
3923 result.push(pad.clone());
3924 }
3925 if i < flat.len() {
3926 result[target] = flat[i].clone();
3927 }
3928 }
3929 Ok(ResultData::List(result))
3930 }
3931 }
3932 "UNIQUE" => {
3933 let Some(arg) = args.first() else {
3934 return Ok(ResultData::Error("#VALUE!".to_string()));
3935 };
3936 let (flat, _cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3937 let exactly_once = match args.get(2) {
3938 Some(e) => self.to_bool(&self.evaluate_ast(e, context, row, col, deps, scope)?),
3939 None => false,
3940 };
3941 let mut seen: Vec<(String, ResultData, usize)> = Vec::new();
3942 for v in &flat {
3943 let key = match v {
3947 ResultData::None => "blank:".to_string(),
3948 ResultData::Boolean(b) => format!("bool:{b}"),
3949 ResultData::Integer(i) => format!("num:{}", *i as f64),
3950 ResultData::Float(f) => format!("num:{f}"),
3951 ResultData::String(s) => format!("str:{s}"),
3952 ResultData::Error(e) => format!("err:{e}"),
3953 ResultData::List(_) | ResultData::Dict(_) => format!("other:{v}"),
3954 };
3955 match seen.iter_mut().find(|(k, ..)| k == &key) {
3956 Some(entry) => entry.2 += 1,
3957 None => seen.push((key, v.clone(), 1)),
3958 }
3959 }
3960 let result: Vec<ResultData> = seen
3961 .into_iter()
3962 .filter(|(_, _, count)| !exactly_once || *count == 1)
3963 .map(|(_, v, _)| v)
3964 .collect();
3965 Ok(ResultData::List(result))
3966 }
3967 "SORT" => {
3968 let Some(arg) = args.first() else {
3969 return Ok(ResultData::Error("#VALUE!".to_string()));
3970 };
3971 let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
3972 let rows = flat.len().checked_div(cols).unwrap_or(0);
3973 let sort_index = match args.get(1) {
3974 Some(e) => self
3975 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3976 .unwrap_or(1.0) as usize,
3977 None => 1,
3978 };
3979 let sort_order = match args.get(2) {
3980 Some(e) => self
3981 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
3982 .unwrap_or(1.0),
3983 None => 1.0,
3984 };
3985 let col_idx = sort_index.saturating_sub(1).min(cols.saturating_sub(1));
3986 let mut row_indices: Vec<usize> = (0..rows).collect();
3987 row_indices.sort_by(|&a, &b| {
3988 Self::sort_compare_blanks_last(
3989 &flat[a * cols + col_idx],
3990 &flat[b * cols + col_idx],
3991 sort_order,
3992 )
3993 });
3994 let mut result = Vec::with_capacity(flat.len());
3995 for r in row_indices {
3996 for c in 0..cols {
3997 result.push(flat[r * cols + c].clone());
3998 }
3999 }
4000 Ok(ResultData::List(result))
4001 }
4002 "SORTBY" => {
4003 if args.len() < 2 {
4004 return Ok(ResultData::Error("#VALUE!".to_string()));
4005 }
4006 let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
4007 let rows = flat.len().checked_div(cols).unwrap_or(0);
4008 let by = self.eval_as_array(&args[1], context, row, col, deps, scope)?;
4009 let order = match args.get(2) {
4010 Some(e) => self
4011 .to_f64(&self.evaluate_ast(e, context, row, col, deps, scope)?)
4012 .unwrap_or(1.0),
4013 None => 1.0,
4014 };
4015 let mut row_indices: Vec<usize> = (0..rows).collect();
4016 row_indices.sort_by(|&a, &b| {
4017 let va = by.get(a).cloned().unwrap_or(ResultData::None);
4018 let vb = by.get(b).cloned().unwrap_or(ResultData::None);
4019 Self::sort_compare_blanks_last(&va, &vb, order)
4020 });
4021 let mut result = Vec::with_capacity(flat.len());
4022 for r in row_indices {
4023 for c in 0..cols {
4024 result.push(flat[r * cols + c].clone());
4025 }
4026 }
4027 Ok(ResultData::List(result))
4028 }
4029 "FILTER" => {
4030 if args.len() < 2 {
4031 return Ok(ResultData::Error("#VALUE!".to_string()));
4032 }
4033 let (flat, cols) = self.array_shape(&args[0], context, row, col, deps, scope)?;
4034 let rows = flat.len().checked_div(cols).unwrap_or(0);
4035 let include = self.eval_as_array(&args[1], context, row, col, deps, scope)?;
4036 let mut result = Vec::new();
4037 for r in 0..rows {
4038 let keep = include.get(r).map(|v| self.to_bool(v)).unwrap_or(false);
4039 if keep {
4040 for c in 0..cols {
4041 result.push(flat[r * cols + c].clone());
4042 }
4043 }
4044 }
4045 if result.is_empty() {
4046 match args.get(2) {
4047 Some(e) => Ok(self.evaluate_ast(e, context, row, col, deps, scope)?),
4048 None => Ok(ResultData::Error("#CALC!".to_string())),
4049 }
4050 } else {
4051 Ok(ResultData::List(result))
4052 }
4053 }
4054 "TRIMRANGE" => {
4055 let Some(arg) = args.first() else {
4056 return Ok(ResultData::Error("#VALUE!".to_string()));
4057 };
4058 let (flat, cols) = self.array_shape(arg, context, row, col, deps, scope)?;
4059 let rows = flat.len().checked_div(cols).unwrap_or(0);
4060 let is_blank = |v: &ResultData| {
4061 matches!(v, ResultData::None)
4062 || matches!(v, ResultData::String(s) if s.is_empty())
4063 };
4064 let row_blank = |r: usize| (0..cols).all(|c| is_blank(&flat[r * cols + c]));
4065 let col_blank = |c: usize| (0..rows).all(|r| is_blank(&flat[r * cols + c]));
4066 let mut r_start = 0;
4067 while r_start < rows && row_blank(r_start) {
4068 r_start += 1;
4069 }
4070 let mut r_end = rows;
4071 while r_end > r_start && row_blank(r_end - 1) {
4072 r_end -= 1;
4073 }
4074 let mut c_start = 0;
4075 while c_start < cols && col_blank(c_start) {
4076 c_start += 1;
4077 }
4078 let mut c_end = cols;
4079 while c_end > c_start && col_blank(c_end - 1) {
4080 c_end -= 1;
4081 }
4082 let mut result = Vec::new();
4083 for r in r_start..r_end {
4084 for c in c_start..c_end {
4085 result.push(flat[r * cols + c].clone());
4086 }
4087 }
4088 Ok(ResultData::List(result))
4089 }
4090 _ => unreachable!(),
4091 }
4092 }
4093
4094 pub fn get_src(&self, cell: &CellRef) -> Option<&String> {
4101 let col = self.columns.get(cell.col);
4102 if let Some(col) = col {
4103 col.src.get(cell.row)
4104 } else {
4105 None
4106 }
4107 }
4108
4109 pub fn get_src_str(&self, cell: &CellRef) -> String {
4112 let col = self.columns.get(cell.col);
4113 if let Some(col) = col {
4114 col.src.get(cell.row).cloned().unwrap_or("".to_string())
4115 } else {
4116 "".to_string()
4117 }
4118 }
4119
4120 pub fn get_src_str_ref(&self, cell: &CellRef) -> Option<&str> {
4122 let col = self.columns.get(cell.col)?;
4123 col.src.get(cell.row).map(|s| s.as_str())
4124 }
4125
4126 pub fn get_word_boundaries(&self, cell: &CellRef, char_offset: usize) -> (usize, usize) {
4130 let text = self.get_src_str(cell);
4131 get_word_boundaries_from_str(&text, char_offset)
4132 }
4133}